Projections using Hypertuned model through XGboost

All data is from FanGraphs. I have no affiliation with FanGraphs, but please consider contributing to their website if you found this project informative.

1 Project Scope

1.1 Objective

This project is designed to showcase how Using a Percentile Based Worth System values Fantasy Baseball Players through a Inning Pitched (IP) weighted projection

The Categories used for prediction valuation are year-end rankings for the following metrics:

  • Wins
  • Saves
  • Strike Outs
  • ERA ( 9 * Earned Runs per Inning Pitched)
  • WHIP (Walks and Hits per Inning Pitched)



2 Processing the Data

2.1 Getting Data Into R

2.1.1 Load Libraries

First we need to load the packages that R needs to run the analysis

library(sqldf) #SQL in R
library(skimr) #Summaries and useful for removing low % data
library(ggplot2) #Plotting Functions
library(plyr) #slightly deprecated data cleaning
library(dplyr) #slightly updated data cleaning
library(tidyverse) #tidyverse data cleaning universe
library(caret) #wrapper for creating, tuning and validating models
library(xgboost) #package for creating regression tree model
library(vtreat) # useful package for treating data before modeling 
library(Matrix)
library(Boruta)
library(mgcv)
library(moments) #for measuring skewness
library(data.table) #alternative to dplyr we use to create lags
library(pdp) #partial dependence graphs
library(vip) #variable importance 
library(grid) #put multiple plots on one grid
library(gridExtra) #additional grid functionality
library(janitor) #one function used to clean transposed data set
library(ggpubr) #for qq plot
library(tableHTML)
library(kableExtra)
library(owmr)

The # comments generally explain what additional functionality each library adds to R

2.1.2 Load in Data

All data is downloaded from Fan Graphs. From this location. The data is also available on my Github here. There are player level and team data sets


#data read-in
pitcher_data <- read_csv("FanGraphs Leaderboard_Pitching20IP.csv")
#Team datasets
FDG_Team = read_csv("FanGraphs Leaderboard_Team.csv")
#Create a prefix for all team stats that starts with T_
FDG_Team2 <- FDG_Team %>% 
  rename_with( ~ paste0("T_", .x))

2.1.3 Checking Team Data

str give information about an object, while skim provides a customizable summary


#Output not shown for space
#str(FDG_Team2)

skim(FDG_Team2) %>%  
  tibble::as_tibble()
NA
NA

2.2 Understanding the Dataset

2.2.1 Exploring the dataset

skim let’s us see how the data was imported into R. Documentation can be found here


#Full Dataset dimensions

skimr::skim(pitcher_data) %>% 
  tibble::as_tibble() %>% 
  select(skim_type,skim_variable,complete_rate) %>% 
  filter(complete_rate >0.30) #288 Variables

#skim_type - character or numeric
#skim_variable - name of variable
#complete_rate - % of data that is not missing
#filter - only keep variables that have 30% of data populated

Additionally let’s look at how variables vary by year to see if there are any discrepancies there


#It looks like one year, there were fewer games played, and there is a clear drop off in home runs
pitcher_data_dist =
pitcher_data %>% 
 group_by(Season) %>% 
  summarize (Max_Games = max(G),
             Avg_W= mean(W)
             )
pitcher_data_dist

ggplot(pitcher_data_dist, aes(Season, Avg_W)) +
  geom_col()+
  ggtitle("Average Wins by Year")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))

NA
NA
NA

2.3 Cleaning and Creating Initial Dataset for Model

What are some issues with the data?

  1. Many of Variables, such as K%, are being read in as characters

    • Only Team and Player Name should be characters
  2. There is spotty data coverage in some of the variables (~Variables have less than 30% Coverage)

  3. 2020 Data only includes 60 games worth of data

    • This was a season shortened due to Covid-19
  4. Team Data needs to be appended to pitcher Data by Team Name


2.3.1 Cleanly Changing all Variables that are characters to numeric.

There are several ways to do this, we will identify the variables we want to change that are mis-identified. parse_number can be used to pull numbers from these variables. Additional ways to tackle this can be found here


#Select Column names that are characters but not Team or Name, These should be percentages
pitcher_data_chars_to_convert <- pitcher_data %>% 
  select_if(is.character)%>% select(-Team,-Name) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Note : There are additional ways to do this, this is just one solution


#We can exclude the variables we converted and reintroduce them
pitcher_data_num <- pitcher_data %>% select(-colnames(pitcher_data_chars_to_convert))

pitcher_data2 = cbind(pitcher_data_num,pitcher_data_chars_to_convert) %>% 
  select (colnames(pitcher_data)) %>%  #preserve original order 
  dplyr::rename(flyball_perc = `FB%...50`,fastball_perc = `FB%...74`) #rename two ambiguous columns
  
skim(pitcher_data2) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()


#Logical variables are R's best guess, in our case they are all NA's and will be removed

The same can be done for the Team Data that is loaded


#Select Column names that are characters but not Team or Name, These should be percentages
FDG_Team2_chars_to_convert <- FDG_Team2 %>% 
  select_if(is.character)%>% select(-T_Team) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Keep in mind, parse number may make actual characters into numerical variables so carefully check your data before using

#We can exclude the variables we converted and reintroduce them
FDG_Team2_num <- FDG_Team2 %>% select(-colnames(FDG_Team2_chars_to_convert))

FDG_Team3 = cbind(FDG_Team2_num,FDG_Team2_chars_to_convert) %>% 
  select (colnames(FDG_Team2)) %>%  #preserve original order
dplyr::rename(T_flyball_perc = `T_FB%...45`,T_fastball_perc = `T_FB%...72`) 

skim(FDG_Team3) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()
NA
NA
NA

2.3.2 Filtering Data with Low Coverage

I choose 30% coverage of data necessary but this can be adjusted up or down. This will also get rid of columns that are all NA.


# Keep variables with enough values (Need 30% data coverage rate here)
Player_cols_to_keep =
skim(pitcher_data2) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)

#Transpose Rows to get column names as skim melts the data
Player_cols_to_keep_transpose = t(Player_cols_to_keep) 

#extract the colnames we would like to keep
Player_cols_to_keep = colnames(janitor::row_to_names(Player_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
pitcher_data3 = pitcher_data2 %>% 
  select(one_of(Player_cols_to_keep)) 

Repeat the process for Team Variables

Team_cols_to_keep =
skim(FDG_Team3) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)


#Transpose Rows to get column names as skim melts the data
Team_cols_to_keep_transpose = t(Team_cols_to_keep) 

#extract the colnames we would like to keep
Team_cols_to_keep = colnames(janitor::row_to_names(Team_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
FDG_Team4 = FDG_Team3 %>% 
  select(one_of(Team_cols_to_keep)) 

2.3.3 Creating Variables Normalized by Year

Some Variables will need to be normalized by Innings_Pitched (IP) if they aren’t a percentage already. Remaining Variables are percentages or indices so will not need to be transformed



pitcher_data4 = pitcher_data3 %>% 
  mutate( #create new variables based on existing variables
    W_IP = W/IP,
    L_IP =  L/IP, 
    ShO_IP = ShO/IP,
    SV_IP = SV/IP,
    BS_IP = BS/IP,
    TBF_IP = TBF/IP,
    H_IP = H/IP,
    R_IP = R/IP,
    ER_IP = ER/IP,
    HR_IP=HR/IP,
    BB_IP=BB/IP,
    IBB_IP=IBB/IP,
    HBP_IP=HBP/IP,
    WP_IP= WP/IP,
    BK_IP=BK/IP,
    SO_IP=SO/IP,
    GB_IP = GB/IP,   #Groundballs
    FB_IP =  FB/IP,  #FlyBalls
    LD_IP = LD/IP,   #LineDrives
    IFFB_IP = IFFB/IP,  #Infield Fly balls
    Balls_IP= Balls/IP,
    Strikes_IP= Strikes/IP,
    Pitches_IP= Pitches/IP,
    RS_IP= RS/IP,
    IFH_IP= IFH/IP,
    BU_IP= BU/IP,
    BUH_IP= BUH/IP,
    Pulls_IP= Pulls/IP,
    HLD_IP= HLD/IP,   
    SD_IP= SD/IP,    
    MD_IP= MD/IP,    
    Barrels_IP= Barrels/IP,
    HardHits_IP= HardHit/IP
  ) %>% select(-L,-G,-IP,-ShO,-BS,-(TBF:BK),-(GB:BUH),-Pulls,-(SD:MD),-Barrels,-HardHit,-Events)
               
               #will be removed after lags -FIP,-(RAR:WPA),,-(wFB:wCH),-(`ERA-`:`xFIP-`),-SIERA,-(`RA9-WAR`:`Age Rng`),-kwERA,-`wCH (pi)`:`wSL (pi)`,`K/9+`:`HR/FB%+`) #Drop the old variables
#Be careful about RS - Run Support and RS/9

#skim(pitcher_data4) %>% as_tibble()

Repeat the process for Team Variables


FDG_Team5 = FDG_Team4 %>% 
  mutate( #create new variables based on existing variables
    T_H_T_PA = T_H/T_PA,
    T_x1B_T_PA = T_1B/T_PA, #note: R can't have variables start with a number
    T_x2b_T_PA = T_2B/T_PA,
    T_x3b_T_PA = T_3B/T_PA,
    T_HR_T_PA = T_HR/T_PA,
    T_R_T_PA = T_R/T_PA,
    T_RBI_T_PA = T_RBI/T_PA,
    T_BB_T_PA = T_BB/T_PA,
    T_IBB_T_PA = T_IBB/T_PA,
    T_SO_T_PA=T_SO/T_PA,
    T_HBP_T_PA=T_HBP/T_PA,
    T_SF_T_PA=T_SF/T_PA,
    T_SH_T_PA=T_SH/T_PA,
    T_GDP_T_PA= T_GDP/T_PA,#ground into double play
    T_SB_T_PA=T_SB/T_PA,
    T_CS_T_PA=T_CS/T_PA,
    T_GB_T_PA = T_GB/T_PA,   #Groundballs
    T_FB_T_PA =  T_FB/T_PA,  #FlyBalls
    T_LD_T_PA = T_LD/T_PA,   #LineDrives
    T_IFFB_T_PA = T_IFFB/T_PA,  #Infield Fly balls
    T_Pitches_T_PA= T_Pitches/T_PA,
    T_Balls_T_PA= T_Balls/T_PA,
    T_Strikes_T_PA= T_Strikes/T_PA,
    T_IFH_T_PA= T_IFH/T_PA,
    T_BU_T_PA= T_BU/T_PA,
    T_BUH_T_PA= T_BUH/T_PA,
    T_PH_T_PA= T_PH/T_PA,
    T_Barrels_T_PA= T_Barrels/T_PA,
    T_HardHits_T_PA= T_HardHit/T_PA
  ) %>% select(-(T_H:T_CS),-(T_GB:T_BUH),-T_PH,-T_Barrels,-T_HardHit,-T_Events) #Drop the old variables


#skim(FDG_Team5) %>% as_tibble()

2.3.4 Creating Lagged Variables

There are several ways to lag a dataset BY GROUP.
* Dplyr way is here..
* While data.table (the method used below) is here.

#Note we will only be lagging the player level data, as the previous year's team performance shouldn't impact current performance


#Order the dataset by lag columns
pitcher_data5 =  arrange(pitcher_data4, playerid,Season) #playerid is the Fangraph id assigned to each player

# Convert dataframe to data.table format
DT_pitcher = data.table(pitcher_data5)

#designate columns to lag - which is all of them
cols1 = colnames(pitcher_data5)
anscols = paste("lag", cols1, sep="_")
DT_pitcher[, (anscols) := data.table::shift(.SD, 1, NA, "lag"),by ='playerid', .SDcols=cols1] #Create 1 period lags by year

pitcher_data6 = as.data.frame(DT_pitcher) %>% select(-lag_playerid, -lag_Team, -lag_Season, -lag_Age,-lag_Name)

ncol(pitcher_data5) #250 - no lags
[1] 251
ncol(pitcher_data6) #495 - lagged data ~ (250 * 2)-5
[1] 497

2.3.5 Merging Team and Player Data

We can use either the merge function or the SQL functionality provided by the sqldf package to join the lagged player level data to the Team level data


df_pitching_init = sqldf(
  "
  select a.*, b.*
  from pitcher_data6 a
  left join FDG_Team5 b
  on a.Team = b.T_Team and a.Season = b.T_Season
  
  "
)  %>% select(-T_Team,-T_Season,-T_Age,-T_G,-T_AB)# Unncessary Team Variables


nrow(df_pitching_init) - nrow(pitcher_data6) #check if any rows are duplicated
[1] 0

3 Creating Rankings for Players Based On Percentiles

We can use Percentile based ranking to get rankings for players from the 2021 season.

3.1 Worth of each stat

3.1.1 Calculating past performance

Each player goes from a 0% to 100% on each percentile stat that is used for creating a scoring opportunity. All data is already normalized by plate appearances, but must now be ranked for each year.


#Categories I include are:
#Wins, Saves, WHIP, ERA, SOs, Holds

df_pitching_init2 =  df_pitching_init %>%
#  arrange(player_id,year) %>% 
  group_by(Season) %>% 
  mutate(
    Wins_share = order(order(rank(W_IP,ties.method = 'average'),decreasing = FALSE))/n(),
     SO_share = order(order(rank(SO_IP,ties.method = 'average'),decreasing = FALSE))/n(),
     SV_share = order(order(rank(SV_IP,ties.method = 'average'),decreasing = FALSE))/n(),
     WHIP_share = order(order(rank(WHIP,ties.method = 'average'),decreasing = FALSE))/n(),
     ERA_share = order(order(rank(ERA,ties.method = 'average'),decreasing = FALSE))/n(),
    HLD_share = 0,
    Worth = Wins_share+SO_share+SV_share+WHIP_share+ERA_share+HLD_share
    ) %>% 
  ungroup() 

Chart of the Distribution of initial percentiles
As the chart below shows, the data is roughly normal.


skewness((df_pitching_init2$Worth))
[1] 0.086
ggplot2::qplot(df_pitching_init2$Worth, main="Total Pitching Worth Dataset") + geom_histogram(colour="black", fill="grey") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

min(df_pitching_init2$Worth)
[1] 0.32
max(df_pitching_init2$Worth)
[1] 4.6
ggpubr::ggqqplot(df_pitching_init2$Worth)


shapiro.test(df_pitching_init2$Worth)

    Shapiro-Wilk normality test

data:  df_pitching_init2$Worth
W = 1, p-value = 0.002

3.2 2021 Player Rankings - Per IP performance

3.2.1 2021 Player Rankings - Top Worth Players

While it looks like many of the top players have low worth scores, it is because we haven’t applied a modifier for IP yet. Wins are harder to come by relative to any other stat and require more innings pitched.



df_pitching_init2_raw =  df_pitching_init %>%
#  arrange(player_id,year) %>% 
  group_by(Season) %>% 
  mutate(
    Wins_share_raw = order(order(rank(W,ties.method = 'average'),decreasing = FALSE))/n(),
     SO_share_raw = order(order(rank(SO,ties.method = 'average'),decreasing = FALSE))/n(),
     SV_share_raw = order(order(rank(SV,ties.method = 'average'),decreasing = FALSE))/n(),
     WHIP_share = order(order(rank(WHIP,ties.method = 'average'),decreasing = FALSE))/n(),
     ERA_share = order(order(rank(ERA,ties.method = 'average'),decreasing = FALSE))/n(),
    HLD_share_raw = 0,
    Worth = Wins_share_raw+SO_share_raw+SV_share_raw+WHIP_share+ERA_share+HLD_share_raw
    ) %>% 
  ungroup() %>% 
select(-W,-SO,-SV,-WHIP,-ERA,-HLD)



options(digits=2)

df_pitching_init2021_raw =
df_pitching_init2_raw %>% 
  group_by(Name) %>% 
  filter(Season == 2021) %>% 
  arrange(desc(Worth)) %>% 
  select(Name,Wins_share_raw,SO_share_raw,SV_share_raw,WHIP_share,ERA_share,HLD_share_raw,Worth)


df_pitching_init2021_raw %>%
  filter (Worth>2.9) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)
Name Wins_share_raw SO_share_raw SV_share_raw WHIP_share ERA_share HLD_share_raw Worth
Daniel Bard 0.79 0.69 0.97 0.87 0.75 0 4.0
Garrett Richards 0.79 0.84 0.86 0.87 0.68 0 4.0
Jesus Luzardo 0.77 0.78 0.55 0.89 0.92 0 3.9
Brady Singer 0.72 0.89 0.66 0.82 0.69 0 3.8
Brad Keller 0.86 0.85 0.31 0.92 0.77 0 3.7
Jose Alvarado 0.82 0.60 0.90 0.87 0.51 0 3.7
Mitch Keller 0.69 0.76 0.40 0.96 0.87 0 3.7
Justus Sheffield 0.81 0.56 0.37 0.98 0.95 0 3.7
Nick Pivetta 0.89 0.95 0.72 0.51 0.60 0 3.7
Rafael Montero 0.65 0.34 0.91 0.81 0.91 0 3.6
Josh Fleming 0.93 0.58 0.78 0.59 0.73 0 3.6
Alec Mills 0.74 0.73 0.71 0.69 0.73 0 3.6
Joe Jimenez 0.75 0.49 0.73 0.80 0.85 0 3.6
Paul Fry 0.58 0.52 0.83 0.80 0.87 0 3.6
Wil Crowe 0.61 0.82 0.53 0.84 0.78 0 3.6
Erick Fedde 0.82 0.87 0.38 0.70 0.78 0 3.5
Adam Ottavino 0.77 0.62 0.93 0.70 0.51 0 3.5
Bryan Garcia 0.50 0.22 0.85 0.98 0.98 0 3.5
Alex Reyes 0.92 0.77 0.98 0.58 0.25 0 3.5
Yusei Kikuchi 0.83 0.94 0.60 0.54 0.57 0 3.5
Zach Davies 0.74 0.83 0.19 0.87 0.83 0 3.5
Tarik Skubal 0.87 0.94 0.64 0.45 0.55 0 3.5
Vladimir Gutierrez 0.90 0.74 0.49 0.66 0.66 0 3.4
Brett de Geus 0.52 0.34 0.65 0.95 0.98 0 3.4
Andrew Heaney 0.86 0.92 0.30 0.53 0.84 0 3.4
Ryan Weathers 0.63 0.63 0.79 0.63 0.76 0 3.4
Patrick Corbin 0.88 0.90 0.09 0.72 0.84 0 3.4
Kris Bubic 0.77 0.84 0.61 0.63 0.58 0 3.4
Daniel Lynch 0.63 0.47 0.62 0.90 0.81 0 3.4
Eduardo Rodriguez 0.97 0.96 0.19 0.64 0.66 0 3.4
J.A. Happ 0.91 0.86 0.07 0.73 0.83 0 3.4
Chris Paddack 0.83 0.79 0.56 0.45 0.73 0 3.4
Cole Irvin 0.93 0.87 0.48 0.55 0.52 0 3.4
Griffin Canning 0.71 0.56 0.54 0.75 0.80 0 3.4
Matt Harvey 0.73 0.77 0.15 0.81 0.88 0 3.3
Keegan Akin 0.37 0.70 0.50 0.85 0.93 0 3.3
Dane Dunning 0.70 0.83 0.50 0.70 0.59 0 3.3
Chris Stratton 0.80 0.73 0.93 0.50 0.37 0 3.3
Cesar Valdez 0.27 0.38 0.92 0.91 0.84 0 3.3
Ben Bowden 0.51 0.35 0.57 0.97 0.92 0 3.3
Tanner Rainey 0.19 0.35 0.87 0.93 0.97 0 3.3
Matt Manning 0.62 0.49 0.58 0.79 0.83 0 3.3
Dallas Keuchel 0.88 0.76 0.09 0.81 0.76 0 3.3
Jeurys Familia 0.88 0.62 0.68 0.66 0.45 0 3.3
Kyle Finnegan 0.67 0.59 0.94 0.74 0.34 0 3.3
Jake Arrieta 0.63 0.70 0.03 0.96 0.97 0 3.3
Jorge Lopez 0.45 0.83 0.25 0.89 0.86 0 3.3
Aroldis Chapman 0.73 0.77 0.99 0.52 0.27 0 3.3
Tanner Scott 0.69 0.61 0.40 0.84 0.74 0 3.3
Dylan Cease 0.98 0.99 0.45 0.42 0.44 0 3.3
James Karinchak 0.83 0.68 0.94 0.35 0.47 0 3.3
Phil Maton 0.75 0.72 0.42 0.72 0.65 0 3.3
Jordan Lyles 0.91 0.91 0.07 0.64 0.74 0 3.3
Gregory Soto 0.76 0.66 0.97 0.59 0.28 0 3.3
Griffin Jax 0.62 0.58 0.57 0.59 0.90 0 3.3
Kyle Hendricks 0.98 0.89 0.16 0.57 0.67 0 3.3
Zach Plesac 0.93 0.80 0.55 0.32 0.64 0 3.2
Hansel Robles 0.43 0.65 0.95 0.62 0.58 0 3.2
Codi Heuer 0.84 0.48 0.85 0.53 0.54 0 3.2
Jon Gray 0.86 0.93 0.28 0.55 0.62 0 3.2
Matt Peacock 0.71 0.43 0.58 0.83 0.69 0 3.2
Carlos Estevez 0.45 0.52 0.94 0.76 0.56 0 3.2
JT Brubaker 0.69 0.88 0.39 0.50 0.77 0 3.2
German Marquez 0.96 0.95 0.28 0.46 0.57 0 3.2
Shane McClanahan 0.93 0.90 0.62 0.46 0.31 0 3.2
Logan Gilbert 0.77 0.88 0.64 0.27 0.65 0 3.2
Ryan Yarbrough 0.89 0.84 0.35 0.38 0.73 0 3.2
Rex Brothers 0.43 0.65 0.69 0.68 0.75 0 3.2
Adbert Alzolay 0.69 0.88 0.75 0.25 0.62 0 3.2
Kenta Maeda 0.76 0.83 0.45 0.51 0.64 0 3.2
Rafael Dolis 0.26 0.31 0.86 0.95 0.81 0 3.2
Amir Garrett 0.04 0.53 0.92 0.83 0.86 0 3.2
Bruce Zimmermann 0.62 0.48 0.58 0.79 0.72 0 3.2
Luis Castillo 0.86 0.96 0.31 0.60 0.46 0 3.2
Ryan Helsley 0.76 0.40 0.75 0.67 0.61 0 3.2
Trevor Stephan 0.51 0.65 0.78 0.66 0.57 0 3.2
Kyle Freeland 0.81 0.82 0.33 0.67 0.55 0 3.2
Luis Garcia 0.95 0.95 0.66 0.30 0.32 0 3.2
Mike Mayers 0.68 0.74 0.83 0.49 0.43 0 3.2
Eli Morgan 0.71 0.69 0.57 0.43 0.76 0 3.2
Sam Hentges 0.20 0.60 0.45 0.96 0.93 0 3.1
Ryan Hendrix 0.71 0.26 0.51 0.82 0.85 0 3.1
Daniel Norris 0.30 0.51 0.71 0.76 0.87 0 3.1
Trevor May 0.78 0.70 0.88 0.43 0.35 0 3.1
Drew Smyly 0.94 0.84 0.15 0.61 0.59 0 3.1
Alex Colome 0.54 0.50 0.96 0.64 0.49 0 3.1
Kwang-hyun Kim 0.84 0.69 0.80 0.49 0.31 0 3.1
Taylor Hearn 0.76 0.76 0.43 0.53 0.63 0 3.1
Brad Hand 0.73 0.53 0.97 0.45 0.44 0 3.1
Kyle Zimmer 0.57 0.38 0.83 0.65 0.68 0 3.1
Adam Plutko 0.18 0.37 0.73 0.90 0.94 0 3.1
Josiah Gray 0.40 0.66 0.66 0.60 0.78 0 3.1
Kyle Funkhouser 0.83 0.57 0.77 0.65 0.30 0 3.1
JC Mejia 0.24 0.40 0.59 0.88 0.99 0 3.1
Tylor Megill 0.62 0.79 0.61 0.48 0.60 0 3.1
Bryan Shaw 0.73 0.62 0.81 0.62 0.32 0 3.1
Josh Sborz 0.60 0.61 0.75 0.67 0.45 0 3.1
Tyler Mahle 0.98 0.98 0.34 0.38 0.40 0 3.1
Hyun-Jin Ryu 0.99 0.90 0.25 0.37 0.56 0 3.1
Austin Gomber 0.89 0.83 0.35 0.39 0.60 0 3.1
Max Kranick 0.39 0.22 0.63 0.94 0.89 0 3.1
Steven Matz 0.98 0.91 0.21 0.55 0.42 0 3.1
Luis Oviedo 0.25 0.20 0.63 0.99 0.99 0 3.1
Martin Perez 0.78 0.77 0.07 0.78 0.66 0 3.1
Spencer Howard 0.11 0.44 0.65 0.88 0.97 0 3.1
Ian Anderson 0.90 0.86 0.55 0.39 0.35 0 3.0
Aaron Nola 0.89 0.99 0.33 0.22 0.63 0 3.0
Jake Brentz 0.69 0.66 0.84 0.47 0.38 0 3.0
Enyel De Los Santos 0.35 0.41 0.44 0.95 0.90 0 3.0
Matt Barnes 0.74 0.71 0.98 0.21 0.41 0 3.0
Matt Andriese 0.29 0.42 0.70 0.88 0.75 0 3.0
Sean Manaea 0.95 0.96 0.32 0.38 0.44 0 3.0
Zac Gallen 0.61 0.90 0.49 0.50 0.54 0 3.0
J.B. Wendelken 0.57 0.31 0.83 0.78 0.54 0 3.0
Greg Holland 0.41 0.45 0.92 0.57 0.68 0 3.0
Alex Young 0.35 0.36 0.44 0.96 0.92 0 3.0
Matt Foster 0.37 0.33 0.77 0.70 0.85 0 3.0
Bryan Abreu 0.47 0.27 0.74 0.73 0.82 0 3.0
Kolby Allard 0.49 0.81 0.46 0.48 0.78 0 3.0
Junior Guerra 0.64 0.53 0.05 0.94 0.86 0 3.0
Heath Hembree 0.29 0.71 0.93 0.30 0.79 0 3.0
Archie Bradley 0.80 0.32 0.82 0.68 0.39 0 3.0
Luis Patino 0.72 0.64 0.65 0.46 0.54 0 3.0
Trevor Williams 0.59 0.74 0.36 0.76 0.55 0 3.0
Caleb Smith 0.57 0.86 0.28 0.62 0.68 0 3.0
David Peterson 0.38 0.61 0.57 0.65 0.79 0 3.0
Jake Diekman 0.41 0.70 0.91 0.56 0.43 0 3.0
Triston McKenzie 0.70 0.89 0.42 0.29 0.70 0 3.0
Jeff Hoffman 0.48 0.68 0.38 0.85 0.61 0 3.0
Lou Trivino 0.81 0.59 0.97 0.41 0.22 0 3.0
Sean Newcomb 0.33 0.36 0.74 0.93 0.65 0 3.0
James Kaprielian 0.87 0.86 0.43 0.37 0.47 0 3.0
Lucas Sims 0.66 0.66 0.92 0.20 0.56 0 3.0
Blake Snell 0.80 0.95 0.21 0.53 0.51 0 3.0
Brent Suter 0.96 0.60 0.72 0.52 0.20 0 3.0
Randy Dobnak 0.25 0.15 0.79 0.82 0.98 0 3.0
Jon Lester 0.78 0.75 0.04 0.78 0.65 0 3.0
Jose Urena 0.55 0.59 0.14 0.87 0.83 0 3.0
Vince Velasquez 0.43 0.80 0.13 0.74 0.89 0 3.0
Jacob Webb 0.70 0.23 0.76 0.79 0.50 0 3.0
Mike Minor 0.85 0.92 0.10 0.39 0.72 0 3.0
Cristian Javier 0.60 0.88 0.84 0.29 0.34 0 3.0
Paul Sewald 0.92 0.81 0.94 0.10 0.19 0 3.0
Chad Kuhl 0.67 0.65 0.28 0.68 0.68 0 3.0
Paolo Espino 0.64 0.75 0.69 0.33 0.53 0 3.0
David Price 0.63 0.49 0.68 0.67 0.46 0 2.9
Jordan Montgomery 0.75 0.94 0.35 0.47 0.43 0 2.9
Aaron Bummer 0.68 0.65 0.84 0.44 0.33 0 2.9
Jose Suarez 0.87 0.72 0.54 0.39 0.40 0 2.9
Paul Campbell 0.39 0.14 0.63 0.86 0.91 0 2.9
Bryse Wilson 0.51 0.39 0.55 0.71 0.77 0 2.9
Carlos Hernandez 0.77 0.64 0.65 0.48 0.38 0 2.9
Hector Neris 0.56 0.78 0.95 0.26 0.37 0 2.9
Stefan Crichton 0.05 0.02 0.89 0.99 0.96 0 2.9
Reid Detmers 0.26 0.04 0.67 0.97 0.97 0 2.9
Aaron Civale 0.96 0.79 0.51 0.21 0.43 0 2.9
Riley Smith 0.23 0.27 0.78 0.77 0.86 0 2.9
NA

3.3 2021 Player Rankings - Actual Performance

3.3.1 2021 Player Rankings - Top Worth Players

Total Rankings for the players (Using 5x5 Scoring) can be found here. While it looks like many of the top players have low worth scores, it is because we haven’t applied a modifier for IP yet.


options(digits=2)

df_pitching_init2021 =
df_pitching_init2 %>% 
  group_by(Name) %>% 
  filter(Season == 2021) %>% 
  arrange(desc(Worth)) %>% 
  select(Name,Wins_share,SO_share,SV_share,WHIP_share,ERA_share,HLD_share,Worth)


df_pitching_init2021 %>%
  filter (Worth>3.9) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)
Name Wins_share SO_share SV_share WHIP_share ERA_share HLD_share Worth
Daniel Bard 0.93 0.85 0.97 0.87 0.75 0 4.4
Joe Jimenez 0.98 0.86 0.76 0.80 0.85 0 4.2
Paul Fry 0.83 0.87 0.82 0.80 0.87 0 4.2
Rafael Dolis 0.63 0.84 0.89 0.95 0.81 0 4.1
Ben Bowden 0.83 0.82 0.57 0.97 0.92 0 4.1
Jose Alvarado 0.96 0.85 0.89 0.87 0.51 0 4.1
Tanner Rainey 0.22 0.93 0.89 0.93 0.97 0 3.9
Sean Newcomb 0.62 0.93 0.79 0.93 0.65 0 3.9
Ryan Hendrix 1.00 0.73 0.51 0.82 0.85 0 3.9
NA

4 Creating Model File

4.1 Additional Data Prep

4.1.1 Remove Variables which are based off current hitting numbers

Not all variables can be used for predictive modeling.

df_pitching_init3 = df_pitching_init2
#Be careful about RS - Run Support and RS/9

  

Lag Share Variables to use for predictive modeling. The variables that we created for the Worth metric must also be removed. This will create the final dataset.


#Order the dataset by lag columns
df_pitching_init4 =  arrange(df_pitching_init3, playerid,Season) #playerid is the Fangraph id assigned to each player

# Convert dataframe to data.table format
DT_pitcher2 = data.table(df_pitching_init4)

#designate columns to lag - just the new shares
cols1 = (c('Wins_share','SO_share','SV_share', 'ERA_share','WHIP_share','HLD_share','Worth'))
anscols = paste("lag", cols1, sep="_") 
DT_pitcher2[, (anscols) := data.table::shift(.SD, 1, NA, "lag"),by ='playerid', .SDcols=cols1] #Create 1 period lags by year

df_pitching_final = as.data.frame(DT_pitcher2) %>% 
  select(-c(Wins_share,SO_share,SV_share, ERA_share,WHIP_share,HLD_share,Name))%>% 
select(-FIP,-(RAR:WPA),-(wFB:wCH),-(`ERA-`:`xFIP-`),
       -SIERA,-(`RA9-WAR`:`Age Rng`),-kwERA,-(`wCH (pi)`:`wSL (pi)`),-(`K/9+`:`HR/FB%+`)) %>% select(-W,-SO,-SV,-HLD,-W_IP,-SO_IP,-SV_IP,-WHIP,-ERA,-HLD_IP)

4.1.2 Creating Training/Test Split

We split the data into Training Data (which is used to create the model) and test data (which is used to validate the model)


set.seed(15674)  # For reproducibility
# Create index for testing and training data
inTrain <- createDataPartition(y = df_pitching_final$Worth, p = 0.80, list = FALSE)
# subset pitching data for training
tr_2021 <- df_pitching_final[inTrain,]
# subset the rest to test and validate trained model
te_2021 <- df_pitching_final[-inTrain,]

nrow(tr_2021)/nrow(df_pitching_final) #check if split is 0.8
[1] 0.8

4.1.3 Treat Missing Data by Imputing Mean Value

Vtreat Package in R is excellent for treating data before using for modeling. Additional documentation can be found here.


4.1.4 Check Distribution of Training Population

The population used for Training should be indicative of Total Population


ggplot2::qplot(tr_treated_2021$Worth, main="Training Set") + geom_histogram(colour="black", fill="grey") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

skewness(tr_treated_2021$Worth) #The skewness is the same as the overall
[1] 0.077

5 Running XGboost Model

To keep things simple with modeling, we’ll turn the training data into simple input variables for caret::train, dropping the response variable and converting the data frame to a matrix. Documentation for this approach to XGboost can be found here.

5.1 Tuning the Model

5.1.1 Initial Non-Tuned Model

Break the data set into x and y inputs with x being a matrix

input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>%                      
   select(!ends_with ("_isBAD")))

input_y <- tr_treated_2021$Worth

XGBoost with Default Hyperparameters
The Variable Importance (caret::varImp(xgb_base_2021, scale = F )) from the caret package shows the contribution of each variable to the initial model. As you can see SLG_plus_ (SLG+) takes up much of the importance as it is derived from SLG (one of the key contributors to Worth). These types of variables will be removed during variable selection in the next step.
XGBoost documentation can be found for more general models here.


#Defaults for xgboost model
grid_default <- expand.grid(
  nrounds = 100,
  max_depth = 6,
  eta = 0.3,
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

#This is a blank train_control set, this will be updated after
train_control <- caret::trainControl(
  method = "none",
  verboseIter = FALSE, # no training log
  allowParallel = TRUE # FALSE for reproducible results 
)

xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 773)

5.2 Further Variable Selection

5.2.1 Remove redundant and highly correlated variables

Selection Removal Step 1: Check for high correlations
Normally, this step is done early, but those steps were reserved for preparing the data


dep_cor1 <- t(as.data.frame(cor(tr_treated_2021[ , colnames(tr_treated_2021) != "Worth"],
                tr_treated_2021$Worth)))
dep_cor1 <-
as.data.frame(t(as.data.frame(dep_cor1)%>% 
  select(!starts_with("lag")) %>% #remove lag variables
  select(!contains("_isBAD")))) 

dep_cor1 <- tibble::rownames_to_column(dep_cor1,"VARIABLES")%>% #remove indicators for missing data
  filter(V1 > 0.40|V1 < -0.3)

dep_cor1

dep_cor2 <- colnames(row_to_names(t(dep_cor1),row_number = 1))

Let’s Remove variables with high correlation to worth metric, and metrics that are calculated after a player’s performance (such as WAR)


input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>% #Remove some variables variables
     select (-RS_IP,-ER_IP,-R_IP,-REW,-RE24,-Clutch,-WPA_slash_LI,-Season #Remove redundant variables or non/weighted variables
) %>%      
select(!ends_with ("_isBAD"))) #indicator variable for missing data

input_y <- tr_treated_2021$Worth

Run the model on the new dataset to make sure the variable importances look fine


#Note Training parameters were set in initial model set up
xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 765)

5.3 Model with new data

5.3.1 Tuning All Hyperparameters

A tune grid allows us to test a large amount of hyper-parameters and find the model with the lowest RMSE for predictions.
However, The more values you want to test and the greater the amount of Cross-Fold Validations (method = "cv"), the greater the computational time it will take. More information on the specific parameters can be found here.


# maximum number of trees
nrounds <- 1000

# note to start nrounds from 200, as smaller learning rates result in errors so
# big with lower starting points that they'll mess the scales
tune_grid <- expand.grid(
  nrounds = seq(from = 100, to = nrounds, by = 50),
  eta = c(0.01, 0.025, 0.05, 0.075, 0.1),
  max_depth = c(2, 4, 6, 8, 10),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

tune_control <- caret::trainControl(
  method = "cv", # cross-validation
  number = 5, # with n folds 
  ## Note this was # out in the original code
  #index = createFolds(tr_treated$Id_clean), # fix the folds
  verboseIter = FALSE, # no training log
  allowParallel = TRUE # FALSE for reproducible results 
)

Running the initial tuning model

#Note I will be timing these runs to give an estimate on how long this model takes to run
start_time <- Sys.time()

xgb_tune_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid,
  method = "xgbTree",
  verbose = FALSE
  ,verbosity = 0
)

end_time <- Sys.time()

end_time - start_time
Time difference of 22 mins

Tuning Plot and Variable Importance

varImp(xgb_tune_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 765)
# helper function for the plots
tuneplot <- function(x, probs = .90) {
  ggplot(x) +
    coord_cartesian(ylim = c(quantile(x$results$RMSE, probs = probs), min(x$results$RMSE))) +
    theme_bw()
}

tuneplot(xgb_tune_2021)


5.3.2 Fine Tuning Model

5.3.2.1 Second Tuning: Maximum Depth and Minimum Child Weight

After fixing the learning rate to 0.1 and we’ll also set maximum depth to 3 +-1 (or +2 if max_depth == 2) to experiment a bit around the suggested best tune in previous step. Then, well fix maximum depth and minimum child weigh

tune_grid2 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = ifelse(xgb_tune_2021$bestTune$max_depth == 2,
    c(xgb_tune_2021$bestTune$max_depth:4),
    xgb_tune_2021$bestTune$max_depth - 1:xgb_tune_2021$bestTune$max_depth + 1),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = c(1, 2, 3),
  subsample = 1
)

xgb_tune2_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid2,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune2_2021)


xgb_tune2_2021$bestTune

varImp(xgb_tune2_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 765)

5.3.2.2 Third Tuning: Column and Row Sampling


tune_grid3 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = 0,
  colsample_bytree = c(0.4, 0.6, 0.8, 1.0),
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = c(0.5, 0.75, 1.0)
)

xgb_tune3_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid3,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune3_2021, probs = .95)


xgb_tune3_2021$bestTune

varImp(xgb_tune3_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 765)

5.3.2.3 Fourth Tuning: Gamma

Next, we again pick the best values from previous step, and now will see whether changing the gamma has any effect on the model fit:

tune_grid4 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = c(0, 0.05,0.1, 0.2,0.4, 0.5, 0.7, 0.9, 1.0),
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)

xgb_tune4_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid4,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune4_2021)
Warning: The shape palette can deal with a maximum of 6 discrete values because more than 6
becomes difficult to discriminate; you have 9. Consider specifying shapes manually if you
must have them.
Warning: Removed 60 rows containing missing values (geom_point).

xgb_tune4_2021$bestTune

varImp(xgb_tune4_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 765)

5.3.2.4 Fifth Tuning: Reducing the Learning Rate

Now, we have tuned the hyperparameters and can start reducing the learning rate to get to the final model:

start_time <- Sys.time()

tune_grid5 <- expand.grid(
  nrounds = seq(from = 100, to = 10000, by = 75),
   eta = c(0.01, 0.015, 0.025,0.035, 0.05,0.75, 0.1),
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = xgb_tune4_2021$bestTune$gamma,
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)



xgb_tune5_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid5,
  method = "xgbTree",
  verbose = TRUE
)

#tuneplot(xgb_tune5_2021)

end_time <- Sys.time()

end_time - start_time
Time difference of 28 mins
xgb_tune5_2021$bestTune

varImp(xgb_tune5_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 765)

5.3.2.5 Fitting Final Model


(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3196 samples
 765 predictor

No pre-processing
Resampling: None 
varImp(xgb_model_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 765)

5.4 Model Performance

5.4.1 Checking Model on Test Split Data

We don’t need to look too closely at are training data as Xgboost will heavily overfit the model based on that data. The more important part is how the model performs on in predicting our Test Sample that was not included.



y_pred_test <- predict(xgb_model_2021, data.matrix(te_treated_2021))

test_stats= cbind((te_treated_2021$Worth),y_pred_test)

test_statsR2 = cor(test_stats[,1],test_stats[,2])^2

print(test_statsR2)
[1] 0.71
y_pred_train <- predict(xgb_model_2021, data.matrix(tr_treated_2021))

train_stats = cbind((tr_treated_2021$Worth),y_pred_train)

train_statsR2 = cor(train_stats[,1],train_stats[,2])^2

print(train_statsR2)
[1] 0.98
#test dataset
x <- select(te_treated_2021, -Worth)
y <- (te_treated_2021$Worth)

(xgb_model_rmse <- ModelMetrics::rmse(y, predict(xgb_model_2021, newdata = x)))
[1] 0.34
holdout_x <- select(tr_treated_2021, -Worth)
holdout_y <- tr_treated_2021$Worth

(xgb_model_rmse <- ModelMetrics::rmse(holdout_y, predict(xgb_model_2021, newdata = holdout_x)))
[1] 0.081

5.4.1.1 Graphical Representation of Model


ggplot2::ggplot() +
  aes(x = test_stats[,1], y = test_stats[,2]) +
  geom_jitter() +
  xlab("Predicted Values") +
  ylab("Actual Values") +
  ggtitle("Results of Pitching Model on Test Data")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))+
  geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'


6 Creating 2022 Projections from Model

6.1 Re-fit model for Important Variables

Now that we have an acceptable model, we can use it to create projections for how well we think players should do in 2022 based on their hitting statistics in 2021. First let’s reduce

  1. Only keep variables with high enough importance in model
variables_to_keep_2022
 [1] "Pitches_IP"                                "TBF_IP"                                   
 [3] "E_minus_F"                                 "TTO_percent_"                             
 [5] "BABIP"                                     "K_slash_9"                                
 [7] "lag_SV_share"                              "RS_slash_9"                               
 [9] "Strikes_IP"                                "pLI"                                      
[11] "inLI"                                      "playerid"                                 
[13] "K_percent_"                                "gmLI"                                     
[15] "Age"                                       "SD_IP"                                    
[17] "BB_IP"                                     "Balls_IP"                                 
[19] "Relieving"                                 "HR_slash_FB"                              
[21] "AVG"                                       "Pulls_IP"                                 
[23] "H_IP"                                      "wFB_slash_C"                              
[25] "H_slash_9"                                 "BS_IP"                                    
[27] "BB_slash_9"                                "LOB_percent_"                             
[29] "HR_IP"                                     "HR_slash_9"                               
[31] "MD_IP"                                     "BB_percent_"                              
[33] "Pace"                                      "IBB_IP"                                   
[35] "wFA_oparen_sc_cparen_"                     "T_Rep"                                    
[37] "exLI"                                      "EV"                                       
[39] "wFA_slash_C_oparen_sc_cparen_"             "T_minus_WPA"                              
[41] "Relief_minus_IP"                           "Hard_percent_plus_"                       
[43] "HBP_IP"                                    "T_vCH_oparen_sc_cparen_"                  
[45] "lag_SV_IP"                                 "O_minus_Contact_percent_oparen_sc_cparen_"
[47] "T_SI_percent_oparen_pi_cparen_"            "Contact_percent_oparen_sc_cparen_"        
[49] "Contact_percent_oparen_pi_cparen_"         "WP_IP"                                    
[51] "HardHits_IP"                               "lag_Barrels_IP"                           
[53] "L_IP"                                      "Contact_percent_"                         
[55] "lag_Wins_share"                            "wSL_slash_C"                              
[57] "Z_minus_Swing_percent_"                    "T_fastball_perc"                          
[59] "wFA_slash_C_oparen_pi_cparen_"             "O_minus_Contact_percent_oparen_pi_cparen_"
[61] "LD_percent_"                               "O_minus_Swing_percent_"                   
[63] "O_minus_Contact_percent_"                  "T_SL_percent_"                            
[65] "T_WPA"                                     "Cent_percent_"                            
[67] "T_FBv"                                     "lag_MD_IP"                                
[69] "K_minus_BB_percent_"                       "lag_Pulls_IP"                             
[71] "lag_H_slash_9_plus_"                       "O_minus_Swing_percent_oparen_pi_cparen_"  
[73] "Soft_percent_plus_"                        "wCU_slash_C_oparen_sc_cparen_"            
[75] "CH_minus_Z_oparen_sc_cparen_"              "wSL_slash_C_oparen_sc_cparen_"            
[77] "IFH_IP"                                    "lag_gmLI"                                 
[79] "T_vFA_oparen_sc_cparen_"                   "Cent_percent_plus_"                       
[81] "T_Strikes_T_PA"                            "Start_minus_IP"                           
[83] "lag_SV"                                    "vFA_oparen_pi_cparen_"                    
[85] "O_minus_Swing_percent_oparen_sc_cparen_"   "HardHit_percent_"                         
[87] "wCH_oparen_sc_cparen_"                     "lag_SD_IP"                                
[89] "T_SL_percent_oparen_pi_cparen_"            "Zone_percent_"                            
[91] "Swing_percent_"                            "wCB_slash_C"                              
[93] "LD_IP"                                     "Barrels_IP"                               

  1. Re-fit model with reduced variable scope


(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2022 <- caret::train(
  x = input_x_2022,
  y = input_y_2022,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3196 samples
 125 predictor

No pre-processing
Resampling: None 
vip(xgb_model_2022, num_features = 30)


unscalevi24 = vi(xgb_model_2022, method="model")

unscalevi24$Importance_perc = with(unscalevi24,Importance/sum(Importance)) 

unscalevi24

save(xgb_model_2022,file = '2022_Pitching5x5_Model.Rdata')

6.2 Get 2022 list of players

6.2.1 Arrange the Data so the Columns are in the exact order as the model

First let’s prepare a file for predicting based on our model object

Data_Predict_2022a = df_pitching_init2 %>% select (one_of(colnames(variables_nolag)),Season,playerid)
Warning: Unknown columns: `H_slash_9_plus_`

6.3 Create Predictions for Model

6.3.1 Run Projections on Players who Played in 2021

This is the raw prediction score per IP for each pitcher


6.3.2 Load in Latest 2022 Projections for Innings Pitched

Downloaded from FanGraphs here.



7 2022 Projections Full

7.1 Table of Pitching Projections (Players who Didn’t Play in 2021 - Recieve an NA)

AdjPredict_Score are normalized to 100

LS0tDQp0aXRsZTogIldlbGNvbWUgdG8gbXkgMjAyMiBQcm9qZWN0aW9ucyBmb3IgUGl0Y2hlcnMgNXg1Ig0KYXV0aG9yOiAiRGFyc2hhbiBQYXRlbCINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdGhlbWU6IHNhbmRzdG9uZQ0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgICBmaWdfY2FwdGlvbjogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNCg0KPGh0bWw+DQoNCjxwPg0KDQpQcm9qZWN0aW9ucyB1c2luZyBIeXBlcnR1bmVkIG1vZGVsIHRocm91Z2ggWEdib29zdA0KDQo8L3A+DQoNCjxwPg0KDQpBbGwgZGF0YSBpcyBmcm9tIFtGYW5HcmFwaHMuXShodHRwczovL3d3dy5mYW5ncmFwaHMuY29tLykgSSBoYXZlIG5vIGFmZmlsaWF0aW9uIHdpdGggRmFuR3JhcGhzLCBidXQgcGxlYXNlIGNvbnNpZGVyIGNvbnRyaWJ1dGluZyB0byB0aGVpciBbd2Vic2l0ZV0oaHR0cHM6Ly9wbHVzLmZhbmdyYXBocy5jb20vc2hvcC8pIGlmIHlvdSBmb3VuZCB0aGlzIHByb2plY3QgaW5mb3JtYXRpdmUuDQoNCjwvcD4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9ICdDOi9Vc2Vycy9BZG1pbi9Eb2N1bWVudHMvTGVhcm5pbmcgUHl0aG9uIEZvbGRlcjEvUHl0aG9uIEVzc2VuY2UgVHJhaW5pbmcvRmFudGFzeS1CYXNlYmFsbCcpDQpvcHRpb25zKGtuaXRyLnRhYmxlLmZvcm1hdCA9ICJodG1sIikgDQpvcHRpb25zKGRpZ2l0cz0yKQ0Kb3B0aW9ucyhzY2lwZW4gPSAxMDApDQpgYGANCg0KIyBQcm9qZWN0IFNjb3BlIHsudGFic2V0fQ0KDQojIyBPYmplY3RpdmUNCg0KVGhpcyBwcm9qZWN0IGlzIGRlc2lnbmVkIHRvIHNob3djYXNlIGhvdyBVc2luZyBhIFBlcmNlbnRpbGUgQmFzZWQgV29ydGggU3lzdGVtIHZhbHVlcyBGYW50YXN5IEJhc2ViYWxsIFBsYXllcnMgdGhyb3VnaCBhIElubmluZyBQaXRjaGVkIChJUCkgd2VpZ2h0ZWQgcHJvamVjdGlvbg0KDQpUaGUgQ2F0ZWdvcmllcyB1c2VkIGZvciBwcmVkaWN0aW9uIHZhbHVhdGlvbiBhcmUgeWVhci1lbmQgcmFua2luZ3MgZm9yIHRoZSBmb2xsb3dpbmcgbWV0cmljczoNCg0KLSAgICBXaW5zDQotICAgU2F2ZXMNCi0gICBTdHJpa2UgT3V0cw0KLSAgIEVSQSAoIDkgXCogRWFybmVkIFJ1bnMgcGVyIElubmluZyBQaXRjaGVkKQ0KLSAgIFdISVAgKFdhbGtzIGFuZCBIaXRzIHBlciBJbm5pbmcgUGl0Y2hlZCkNCg0KIVtdKEludHJvQ2hhcnQ2eDYucG5nKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIFByb2Nlc3NpbmcgdGhlIERhdGEgey50YWJzZXR9DQoNCiMjIEdldHRpbmcgRGF0YSBJbnRvIFINCg0KIyMjIExvYWQgTGlicmFyaWVzDQoNCjxwIHN0eWxlPSJjb2xvcjpibGFjazsiPg0KDQoqRmlyc3Qgd2UgbmVlZCB0byBsb2FkIHRoZSBwYWNrYWdlcyB0aGF0IFIgbmVlZHMgdG8gcnVuIHRoZSBhbmFseXNpcyoNCg0KPC9wPg0KDQpgYGB7ciBsb2FkIGxpYnJhcnksbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHNxbGRmKSAjU1FMIGluIFINCmxpYnJhcnkoc2tpbXIpICNTdW1tYXJpZXMgYW5kIHVzZWZ1bCBmb3IgcmVtb3ZpbmcgbG93ICUgZGF0YQ0KbGlicmFyeShnZ3Bsb3QyKSAjUGxvdHRpbmcgRnVuY3Rpb25zDQpsaWJyYXJ5KHBseXIpICNzbGlnaHRseSBkZXByZWNhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkoZHBseXIpICNzbGlnaHRseSB1cGRhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkodGlkeXZlcnNlKSAjdGlkeXZlcnNlIGRhdGEgY2xlYW5pbmcgdW5pdmVyc2UNCmxpYnJhcnkoY2FyZXQpICN3cmFwcGVyIGZvciBjcmVhdGluZywgdHVuaW5nIGFuZCB2YWxpZGF0aW5nIG1vZGVscw0KbGlicmFyeSh4Z2Jvb3N0KSAjcGFja2FnZSBmb3IgY3JlYXRpbmcgcmVncmVzc2lvbiB0cmVlIG1vZGVsDQpsaWJyYXJ5KHZ0cmVhdCkgIyB1c2VmdWwgcGFja2FnZSBmb3IgdHJlYXRpbmcgZGF0YSBiZWZvcmUgbW9kZWxpbmcgDQpsaWJyYXJ5KE1hdHJpeCkgI2NyZWF0aW5nIG1hdHJpY2llcyBmb3IgeGdib29zdA0KbGlicmFyeShtZ2N2KQ0KbGlicmFyeShtb21lbnRzKSAjZm9yIG1lYXN1cmluZyBza2V3bmVzcw0KbGlicmFyeShkYXRhLnRhYmxlKSAjYWx0ZXJuYXRpdmUgdG8gZHBseXIgd2UgdXNlIHRvIGNyZWF0ZSBsYWdzDQpsaWJyYXJ5KHBkcCkgI3BhcnRpYWwgZGVwZW5kZW5jZSBncmFwaHMNCmxpYnJhcnkodmlwKSAjdmFyaWFibGUgaW1wb3J0YW5jZSANCmxpYnJhcnkoZ3JpZCkgI3B1dCBtdWx0aXBsZSBwbG90cyBvbiBvbmUgZ3JpZA0KbGlicmFyeShncmlkRXh0cmEpICNhZGRpdGlvbmFsIGdyaWQgZnVuY3Rpb25hbGl0eQ0KbGlicmFyeShqYW5pdG9yKSAjb25lIGZ1bmN0aW9uIHVzZWQgdG8gY2xlYW4gdHJhbnNwb3NlZCBkYXRhIHNldA0KbGlicmFyeShnZ3B1YnIpICNmb3IgcXEgcGxvdCANCmxpYnJhcnkob3dtcikgI1JlbW92aW5nIFByZWZpeGVzDQpsaWJyYXJ5KGthYmxlRXh0cmEpICMgZm9ybWF0dGluZyBIVE1MIFRhYmxlcw0KbGlicmFyeShmb3JtYXR0YWJsZSkgIyBmb3JtYXR0aW5nIEhUTUwgVGFibGVzDQpgYGANCg0KVGhlIFwjIGNvbW1lbnRzIGdlbmVyYWxseSBleHBsYWluIHdoYXQgYWRkaXRpb25hbCBmdW5jdGlvbmFsaXR5IGVhY2ggbGlicmFyeSBhZGRzIHRvIFINCg0KIyMjIExvYWQgaW4gRGF0YQ0KDQpBbGwgZGF0YSBpcyBkb3dubG9hZGVkIGZyb20gRmFuIEdyYXBocy4gRnJvbSB0aGlzIFtsb2NhdGlvbl0oaHR0cHM6Ly93d3cuZmFuZ3JhcGhzLmNvbS9sZWFkZXJzLmFzcHg/cG9zPWFsbCZzdGF0cz1waXQmbGc9YWxsJnF1YWw9MCZ0eXBlPTcmc2Vhc29uPTIwMjEmbW9udGg9MCZzZWFzb24xPTIwMTUmaW5kPTEmdGVhbT0wJnJvc3Q9MCZhZ2U9MCZmaWx0ZXI9JnBsYXllcnM9MCZzdGFydGRhdGU9MjAxNS0wMS0wMSZlbmRkYXRlPTIwMjEtMTItMzEpLiBUaGUgZGF0YSBpcyBhbHNvIGF2YWlsYWJsZSBvbiBteSBHaXRodWIgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9kaXNzaXBhdGlvbi9GYW50YXN5LUJhc2ViYWxsKS4gVGhlcmUgYXJlIHBsYXllciBsZXZlbCBhbmQgdGVhbSBkYXRhIHNldHMNCg0KYGBge3IgZGF0YSByZWFkLWluLCByZXN1bHRzPSAnaGlkZScsbWVzc2FnZT1GQUxTRX0NCg0KI2RhdGEgcmVhZC1pbg0KcGl0Y2hlcl9kYXRhIDwtIHJlYWRfY3N2KCJGYW5HcmFwaHMgTGVhZGVyYm9hcmRfUGl0Y2hpbmcyMElQLmNzdiIpDQoNCiNUZWFtIGRhdGFzZXRzDQpGREdfVGVhbSA9IHJlYWRfY3N2KCJGYW5HcmFwaHMgTGVhZGVyYm9hcmRfVGVhbS5jc3YiKQ0KDQoNCiNDcmVhdGUgYSBwcmVmaXggZm9yIGFsbCB0ZWFtIHN0YXRzIHRoYXQgc3RhcnRzIHdpdGggVF8NCkZER19UZWFtMiA8LSBGREdfVGVhbSAlPiUgDQogIHJlbmFtZV93aXRoKCB+IHBhc3RlMCgiVF8iLCAueCkpDQoNCmBgYA0KDQojIyMgQ2hlY2tpbmcgVGVhbSBEYXRhDQoNCmBzdHJgIGdpdmUgaW5mb3JtYXRpb24gYWJvdXQgYW4gb2JqZWN0LCB3aGlsZSBgc2tpbWAgcHJvdmlkZXMgYSBjdXN0b21pemFibGUgc3VtbWFyeSAgDQoNCmBgYHtyIGNoZWNraW5nIHRlYW0gZGF0YX0NCg0KI091dHB1dCBub3Qgc2hvd24gZm9yIHNwYWNlDQojc3RyKEZER19UZWFtMikNCg0Kc2tpbShGREdfVGVhbTIpICU+JSAgDQogIHRpYmJsZTo6YXNfdGliYmxlKCkNCg0KDQpgYGANCioqKiAgDQoNCiMjIFVuZGVyc3RhbmRpbmcgdGhlIERhdGFzZXQNCg0KIyMjIEV4cGxvcmluZyB0aGUgZGF0YXNldA0KDQpgc2tpbWAgbGV0J3MgdXMgc2VlIGhvdyB0aGUgZGF0YSB3YXMgaW1wb3J0ZWQgaW50byBSLiBEb2N1bWVudGF0aW9uIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NraW1yL3ZpZ25ldHRlcy9za2ltci5odG1sKQ0KDQpgYGB7cn0NCg0KI0Z1bGwgRGF0YXNldCBkaW1lbnNpb25zDQoNCnNraW1yOjpza2ltKHBpdGNoZXJfZGF0YSkgJT4lIA0KICB0aWJibGU6OmFzX3RpYmJsZSgpICU+JSANCiAgc2VsZWN0KHNraW1fdHlwZSxza2ltX3ZhcmlhYmxlLGNvbXBsZXRlX3JhdGUpICU+JSANCiAgZmlsdGVyKGNvbXBsZXRlX3JhdGUgPjAuMzApICMyODggVmFyaWFibGVzDQoNCiNza2ltX3R5cGUgLSBjaGFyYWN0ZXIgb3IgbnVtZXJpYw0KI3NraW1fdmFyaWFibGUgLSBuYW1lIG9mIHZhcmlhYmxlDQojY29tcGxldGVfcmF0ZSAtICUgb2YgZGF0YSB0aGF0IGlzIG5vdCBtaXNzaW5nDQojZmlsdGVyIC0gb25seSBrZWVwIHZhcmlhYmxlcyB0aGF0IGhhdmUgMzAlIG9mIGRhdGEgcG9wdWxhdGVkDQpgYGANCg0KQWRkaXRpb25hbGx5IGxldCdzIGxvb2sgYXQgaG93IHZhcmlhYmxlcyB2YXJ5IGJ5IHllYXIgdG8gc2VlIGlmIHRoZXJlIGFyZSBhbnkgZGlzY3JlcGFuY2llcyB0aGVyZSAgDQoNCmBgYHtyfQ0KDQojSXQgbG9va3MgbGlrZSBvbmUgeWVhciwgdGhlcmUgd2VyZSBmZXdlciBnYW1lcyBwbGF5ZWQsIGFuZCB0aGVyZSBpcyBhIGNsZWFyIGRyb3Agb2ZmIGluIGhvbWUgcnVucw0KcGl0Y2hlcl9kYXRhX2Rpc3QgPQ0KcGl0Y2hlcl9kYXRhICU+JSANCiBncm91cF9ieShTZWFzb24pICU+JSANCiAgc3VtbWFyaXplIChNYXhfR2FtZXMgPSBtYXgoRyksDQogICAgICAgICAgICAgQXZnX1c9IG1lYW4oVykNCiAgICAgICAgICAgICApDQpwaXRjaGVyX2RhdGFfZGlzdA0KDQpnZ3Bsb3QocGl0Y2hlcl9kYXRhX2Rpc3QsIGFlcyhTZWFzb24sIEF2Z19XKSkgKw0KICBnZW9tX2NvbCgpKw0KICBnZ3RpdGxlKCJBdmVyYWdlIFdpbnMgYnkgWWVhciIpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LHNpemUgPSAyMixjb2xvciA9InN0ZWVsIGJsdWUiKSkNCg0KDQoNCmBgYA0KDQoqKiogIA0KDQojIyBDbGVhbmluZyBhbmQgQ3JlYXRpbmcgSW5pdGlhbCBEYXRhc2V0IGZvciBNb2RlbA0KDQpXaGF0IGFyZSBzb21lIGlzc3VlcyB3aXRoIHRoZSBkYXRhPw0KDQoxLiAgTWFueSBvZiBWYXJpYWJsZXMsIHN1Y2ggYXMgSyUsIGFyZSBiZWluZyByZWFkIGluIGFzIGNoYXJhY3RlcnMNCg0KICAgIC0gICBPbmx5IFRlYW0gYW5kIFBsYXllciBOYW1lIHNob3VsZCBiZSBjaGFyYWN0ZXJzDQoNCjIuICBUaGVyZSBpcyBzcG90dHkgZGF0YSBjb3ZlcmFnZSBpbiBzb21lIG9mIHRoZSB2YXJpYWJsZXMgKFx+VmFyaWFibGVzIGhhdmUgbGVzcyB0aGFuIDMwJSBDb3ZlcmFnZSkNCg0KMy4gIDIwMjAgRGF0YSBvbmx5IGluY2x1ZGVzIDYwIGdhbWVzIHdvcnRoIG9mIGRhdGENCg0KICAgIC0gICBUaGlzIHdhcyBhIHNlYXNvbiBzaG9ydGVuZWQgZHVlIHRvIENvdmlkLTE5DQoNCjQuICBUZWFtIERhdGEgbmVlZHMgdG8gYmUgYXBwZW5kZWQgdG8gcGl0Y2hlciBEYXRhIGJ5IFRlYW0gTmFtZSANCg0KKioqICANCg0KIyMjICoqQ2xlYW5seSBDaGFuZ2luZyBhbGwgVmFyaWFibGVzIHRoYXQgYXJlIGNoYXJhY3RlcnMgdG8gbnVtZXJpYy4qKg0KVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBkbyB0aGlzLCB3ZSB3aWxsIGlkZW50aWZ5IHRoZSB2YXJpYWJsZXMgd2Ugd2FudCB0byBjaGFuZ2UgdGhhdCBhcmUgbWlzLWlkZW50aWZpZWQuIGBwYXJzZV9udW1iZXJgIGNhbiBiZSB1c2VkIHRvIHB1bGwgbnVtYmVycyBmcm9tIHRoZXNlIHZhcmlhYmxlcy4gQWRkaXRpb25hbCB3YXlzIHRvIHRhY2tsZSB0aGlzIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvODMyOTA1OS9ob3ctdG8tY29udmVydC1jaGFyYWN0ZXItb2YtcGVyY2VudGFnZS1pbnRvLW51bWVyaWMtaW4tcikNCg0KYGBge3J9DQoNCiNTZWxlY3QgQ29sdW1uIG5hbWVzIHRoYXQgYXJlIGNoYXJhY3RlcnMgYnV0IG5vdCBUZWFtIG9yIE5hbWUsIFRoZXNlIHNob3VsZCBiZSBwZXJjZW50YWdlcw0KcGl0Y2hlcl9kYXRhX2NoYXJzX3RvX2NvbnZlcnQgPC0gcGl0Y2hlcl9kYXRhICU+JSANCiAgc2VsZWN0X2lmKGlzLmNoYXJhY3RlciklPiUgc2VsZWN0KC1UZWFtLC1OYW1lKSAlPiUgDQogIG11dGF0ZV9hbGwgKGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMocmVhZHI6OnBhcnNlX251bWJlcih4KSkvMTAwKQ0KI05vdGUgOiBUaGVyZSBhcmUgYWRkaXRpb25hbCB3YXlzIHRvIGRvIHRoaXMsIHRoaXMgaXMganVzdCBvbmUgc29sdXRpb24NCg0KDQojV2UgY2FuIGV4Y2x1ZGUgdGhlIHZhcmlhYmxlcyB3ZSBjb252ZXJ0ZWQgYW5kIHJlaW50cm9kdWNlIHRoZW0NCnBpdGNoZXJfZGF0YV9udW0gPC0gcGl0Y2hlcl9kYXRhICU+JSBzZWxlY3QoLWNvbG5hbWVzKHBpdGNoZXJfZGF0YV9jaGFyc190b19jb252ZXJ0KSkNCg0KcGl0Y2hlcl9kYXRhMiA9IGNiaW5kKHBpdGNoZXJfZGF0YV9udW0scGl0Y2hlcl9kYXRhX2NoYXJzX3RvX2NvbnZlcnQpICU+JSANCiAgc2VsZWN0IChjb2xuYW1lcyhwaXRjaGVyX2RhdGEpKSAlPiUgICNwcmVzZXJ2ZSBvcmlnaW5hbCBvcmRlciANCiAgZHBseXI6OnJlbmFtZShmbHliYWxsX3BlcmMgPSBgRkIlLi4uNTBgLGZhc3RiYWxsX3BlcmMgPSBgRkIlLi4uNzRgKSAjcmVuYW1lIHR3byBhbWJpZ3VvdXMgY29sdW1ucw0KICANCnNraW0ocGl0Y2hlcl9kYXRhMikgJT4lIA0KICBhc190aWJibGUoKSAlPiUgDQogIGdyb3VwX2J5KHNraW1fdHlwZSkgJT4lIA0KICBjb3VudCgpDQoNCg0KI0xvZ2ljYWwgdmFyaWFibGVzIGFyZSBSJ3MgYmVzdCBndWVzcywgaW4gb3VyIGNhc2UgdGhleSBhcmUgYWxsIE5BJ3MgYW5kIHdpbGwgYmUgcmVtb3ZlZA0KDQpgYGANCg0KVGhlIHNhbWUgY2FuIGJlIGRvbmUgZm9yIHRoZSBUZWFtIERhdGEgdGhhdCBpcyBsb2FkZWQgIA0KDQoNCmBgYHtyfQ0KDQojU2VsZWN0IENvbHVtbiBuYW1lcyB0aGF0IGFyZSBjaGFyYWN0ZXJzIGJ1dCBub3QgVGVhbSBvciBOYW1lLCBUaGVzZSBzaG91bGQgYmUgcGVyY2VudGFnZXMNCkZER19UZWFtMl9jaGFyc190b19jb252ZXJ0IDwtIEZER19UZWFtMiAlPiUgDQogIHNlbGVjdF9pZihpcy5jaGFyYWN0ZXIpJT4lIHNlbGVjdCgtVF9UZWFtKSAlPiUgDQogIG11dGF0ZV9hbGwgKGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMocmVhZHI6OnBhcnNlX251bWJlcih4KSkvMTAwKQ0KI0tlZXAgaW4gbWluZCwgcGFyc2UgbnVtYmVyIG1heSBtYWtlIGFjdHVhbCBjaGFyYWN0ZXJzIGludG8gbnVtZXJpY2FsIHZhcmlhYmxlcyBzbyBjYXJlZnVsbHkgY2hlY2sgeW91ciBkYXRhIGJlZm9yZSB1c2luZw0KDQojV2UgY2FuIGV4Y2x1ZGUgdGhlIHZhcmlhYmxlcyB3ZSBjb252ZXJ0ZWQgYW5kIHJlaW50cm9kdWNlIHRoZW0NCkZER19UZWFtMl9udW0gPC0gRkRHX1RlYW0yICU+JSBzZWxlY3QoLWNvbG5hbWVzKEZER19UZWFtMl9jaGFyc190b19jb252ZXJ0KSkNCg0KRkRHX1RlYW0zID0gY2JpbmQoRkRHX1RlYW0yX251bSxGREdfVGVhbTJfY2hhcnNfdG9fY29udmVydCkgJT4lIA0KICBzZWxlY3QgKGNvbG5hbWVzKEZER19UZWFtMikpICU+JSAgI3ByZXNlcnZlIG9yaWdpbmFsIG9yZGVyDQpkcGx5cjo6cmVuYW1lKFRfZmx5YmFsbF9wZXJjID0gYFRfRkIlLi4uNDVgLFRfZmFzdGJhbGxfcGVyYyA9IGBUX0ZCJS4uLjcyYCkgDQoNCnNraW0oRkRHX1RlYW0zKSAlPiUgDQogIGFzX3RpYmJsZSgpICU+JSANCiAgZ3JvdXBfYnkoc2tpbV90eXBlKSAlPiUgDQogIGNvdW50KCkNCg0KDQoNCmBgYA0KKioqICANCg0KDQojIyMgRmlsdGVyaW5nIERhdGEgd2l0aCBMb3cgQ292ZXJhZ2UgICAgDQpJIGNob29zZSAzMCUgY292ZXJhZ2Ugb2YgZGF0YSBuZWNlc3NhcnkgYnV0IHRoaXMgY2FuIGJlIGFkanVzdGVkIHVwIG9yIGRvd24uIFRoaXMgd2lsbCBhbHNvIGdldCByaWQgb2YgY29sdW1ucyB0aGF0IGFyZSBhbGwgYE5BYC4gIA0KYGBge3J9DQoNCiMgS2VlcCB2YXJpYWJsZXMgd2l0aCBlbm91Z2ggdmFsdWVzIChOZWVkIDMwJSBkYXRhIGNvdmVyYWdlIHJhdGUgaGVyZSkNClBsYXllcl9jb2xzX3RvX2tlZXAgPQ0Kc2tpbShwaXRjaGVyX2RhdGEyKSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2tpbV90eXBlLCBza2ltX3ZhcmlhYmxlLCBjb21wbGV0ZV9yYXRlKSAlPiUgDQogIGZpbHRlciAoY29tcGxldGVfcmF0ZSA+IDAuMzApDQoNCiNUcmFuc3Bvc2UgUm93cyB0byBnZXQgY29sdW1uIG5hbWVzIGFzIHNraW0gbWVsdHMgdGhlIGRhdGENClBsYXllcl9jb2xzX3RvX2tlZXBfdHJhbnNwb3NlID0gdChQbGF5ZXJfY29sc190b19rZWVwKSANCg0KI2V4dHJhY3QgdGhlIGNvbG5hbWVzIHdlIHdvdWxkIGxpa2UgdG8ga2VlcA0KUGxheWVyX2NvbHNfdG9fa2VlcCA9IGNvbG5hbWVzKGphbml0b3I6OnJvd190b19uYW1lcyhQbGF5ZXJfY29sc190b19rZWVwX3RyYW5zcG9zZSxyb3dfbnVtYmVyID0gMikpDQoNCiNPbmx5IGtlZXAgdGhlIGNvbHVtbnMgZGVzaWduYXRlZCB0byBoYXZlIG92ZXIgMzAlIG9mIHRoZWlyIGRhdGEgcG9wdWxhdGVkIG9yIGdyZWF0ZXINCnBpdGNoZXJfZGF0YTMgPSBwaXRjaGVyX2RhdGEyICU+JSANCiAgc2VsZWN0KG9uZV9vZihQbGF5ZXJfY29sc190b19rZWVwKSkgDQoNCg0KYGBgDQoNCg0KKlJlcGVhdCB0aGUgcHJvY2VzcyBmb3IgVGVhbSBWYXJpYWJsZXMqDQpgYGB7cn0NClRlYW1fY29sc190b19rZWVwID0NCnNraW0oRkRHX1RlYW0zKSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2tpbV90eXBlLCBza2ltX3ZhcmlhYmxlLCBjb21wbGV0ZV9yYXRlKSAlPiUgDQogIGZpbHRlciAoY29tcGxldGVfcmF0ZSA+IDAuMzApDQoNCg0KI1RyYW5zcG9zZSBSb3dzIHRvIGdldCBjb2x1bW4gbmFtZXMgYXMgc2tpbSBtZWx0cyB0aGUgZGF0YQ0KVGVhbV9jb2xzX3RvX2tlZXBfdHJhbnNwb3NlID0gdChUZWFtX2NvbHNfdG9fa2VlcCkgDQoNCiNleHRyYWN0IHRoZSBjb2xuYW1lcyB3ZSB3b3VsZCBsaWtlIHRvIGtlZXANClRlYW1fY29sc190b19rZWVwID0gY29sbmFtZXMoamFuaXRvcjo6cm93X3RvX25hbWVzKFRlYW1fY29sc190b19rZWVwX3RyYW5zcG9zZSxyb3dfbnVtYmVyID0gMikpDQoNCiNPbmx5IGtlZXAgdGhlIGNvbHVtbnMgZGVzaWduYXRlZCB0byBoYXZlIG92ZXIgMzAlIG9mIHRoZWlyIGRhdGEgcG9wdWxhdGVkIG9yIGdyZWF0ZXINCkZER19UZWFtNCA9IEZER19UZWFtMyAlPiUgDQogIHNlbGVjdChvbmVfb2YoVGVhbV9jb2xzX3RvX2tlZXApKSANCg0KDQoNCmBgYA0KDQoNCg0KKioqICANCg0KIyMjIENyZWF0aW5nIFZhcmlhYmxlcyBOb3JtYWxpemVkIGJ5IFllYXIgIA0KU29tZSBWYXJpYWJsZXMgd2lsbCBuZWVkIHRvIGJlIG5vcm1hbGl6ZWQgYnkgSW5uaW5nc19QaXRjaGVkIChJUCkgaWYgdGhleSBhcmVuJ3QgYSBwZXJjZW50YWdlIGFscmVhZHkuIFJlbWFpbmluZyBWYXJpYWJsZXMgYXJlIHBlcmNlbnRhZ2VzIG9yIGluZGljZXMgc28gd2lsbCBub3QgbmVlZCB0byBiZSB0cmFuc2Zvcm1lZA0KDQpgYGB7cn0NCg0KDQpwaXRjaGVyX2RhdGE0ID0gcGl0Y2hlcl9kYXRhMyAlPiUgDQogIG11dGF0ZSggI2NyZWF0ZSBuZXcgdmFyaWFibGVzIGJhc2VkIG9uIGV4aXN0aW5nIHZhcmlhYmxlcw0KICAgIFdfSVAgPSBXL0lQLA0KICAgIExfSVAgPSAgTC9JUCwgDQogICAgU2hPX0lQID0gU2hPL0lQLA0KICAgIFNWX0lQID0gU1YvSVAsDQogICAgQlNfSVAgPSBCUy9JUCwNCiAgICBUQkZfSVAgPSBUQkYvSVAsDQogICAgSF9JUCA9IEgvSVAsDQogICAgUl9JUCA9IFIvSVAsDQogICAgRVJfSVAgPSBFUi9JUCwNCiAgICBIUl9JUD1IUi9JUCwNCiAgICBCQl9JUD1CQi9JUCwNCiAgICBJQkJfSVA9SUJCL0lQLA0KICAgIEhCUF9JUD1IQlAvSVAsDQogICAgV1BfSVA9IFdQL0lQLA0KICAgIEJLX0lQPUJLL0lQLA0KICAgIFNPX0lQPVNPL0lQLA0KICAgIEdCX0lQID0gR0IvSVAsICAgI0dyb3VuZGJhbGxzDQogICAgRkJfSVAgPSAgRkIvSVAsICAjRmx5QmFsbHMNCiAgICBMRF9JUCA9IExEL0lQLCAgICNMaW5lRHJpdmVzDQogICAgSUZGQl9JUCA9IElGRkIvSVAsICAjSW5maWVsZCBGbHkgYmFsbHMNCiAgICBCYWxsc19JUD0gQmFsbHMvSVAsDQogICAgU3RyaWtlc19JUD0gU3RyaWtlcy9JUCwNCiAgICBQaXRjaGVzX0lQPSBQaXRjaGVzL0lQLA0KICAgIFJTX0lQPSBSUy9JUCwNCiAgICBJRkhfSVA9IElGSC9JUCwNCiAgICBCVV9JUD0gQlUvSVAsDQogICAgQlVIX0lQPSBCVUgvSVAsDQogICAgUHVsbHNfSVA9IFB1bGxzL0lQLA0KICAgIEhMRF9JUD0gSExEL0lQLCAgIA0KICAgIFNEX0lQPSBTRC9JUCwgICAgDQogICAgTURfSVA9IE1EL0lQLCAgICANCiAgICBCYXJyZWxzX0lQPSBCYXJyZWxzL0lQLA0KICAgIEhhcmRIaXRzX0lQPSBIYXJkSGl0L0lQDQogICkgJT4lIHNlbGVjdCgtTCwtRywtSVAsLVNoTywtQlMsLShUQkY6QkspLC0oR0I6QlVIKSwtUHVsbHMsLShTRDpNRCksLUJhcnJlbHMsLUhhcmRIaXQsLUV2ZW50cykNCiAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgI3dpbGwgYmUgcmVtb3ZlZCBhZnRlciBsYWdzIC1GSVAsLShSQVI6V1BBKSwsLSh3RkI6d0NIKSwtKGBFUkEtYDpgeEZJUC1gKSwtU0lFUkEsLShgUkE5LVdBUmA6YEFnZSBSbmdgKSwta3dFUkEsLWB3Q0ggKHBpKWA6YHdTTCAocGkpYCxgSy85K2A6YEhSL0ZCJStgKSAjRHJvcCB0aGUgb2xkIHZhcmlhYmxlcw0KI0JlIGNhcmVmdWwgYWJvdXQgUlMgLSBSdW4gU3VwcG9ydCBhbmQgUlMvOQ0KDQojc2tpbShwaXRjaGVyX2RhdGE0KSAlPiUgYXNfdGliYmxlKCkNCg0KDQoNCg0KYGBgDQoNCipSZXBlYXQgdGhlIHByb2Nlc3MgZm9yIFRlYW0gVmFyaWFibGVzKg0KYGBge3J9DQoNCkZER19UZWFtNSA9IEZER19UZWFtNCAlPiUgDQogIG11dGF0ZSggI2NyZWF0ZSBuZXcgdmFyaWFibGVzIGJhc2VkIG9uIGV4aXN0aW5nIHZhcmlhYmxlcw0KICAgIFRfSF9UX1BBID0gVF9IL1RfUEEsDQogICAgVF94MUJfVF9QQSA9IFRfMUIvVF9QQSwgI25vdGU6IFIgY2FuJ3QgaGF2ZSB2YXJpYWJsZXMgc3RhcnQgd2l0aCBhIG51bWJlcg0KICAgIFRfeDJiX1RfUEEgPSBUXzJCL1RfUEEsDQogICAgVF94M2JfVF9QQSA9IFRfM0IvVF9QQSwNCiAgICBUX0hSX1RfUEEgPSBUX0hSL1RfUEEsDQogICAgVF9SX1RfUEEgPSBUX1IvVF9QQSwNCiAgICBUX1JCSV9UX1BBID0gVF9SQkkvVF9QQSwNCiAgICBUX0JCX1RfUEEgPSBUX0JCL1RfUEEsDQogICAgVF9JQkJfVF9QQSA9IFRfSUJCL1RfUEEsDQogICAgVF9TT19UX1BBPVRfU08vVF9QQSwNCiAgICBUX0hCUF9UX1BBPVRfSEJQL1RfUEEsDQogICAgVF9TRl9UX1BBPVRfU0YvVF9QQSwNCiAgICBUX1NIX1RfUEE9VF9TSC9UX1BBLA0KICAgIFRfR0RQX1RfUEE9IFRfR0RQL1RfUEEsI2dyb3VuZCBpbnRvIGRvdWJsZSBwbGF5DQogICAgVF9TQl9UX1BBPVRfU0IvVF9QQSwNCiAgICBUX0NTX1RfUEE9VF9DUy9UX1BBLA0KICAgIFRfR0JfVF9QQSA9IFRfR0IvVF9QQSwgICAjR3JvdW5kYmFsbHMNCiAgICBUX0ZCX1RfUEEgPSAgVF9GQi9UX1BBLCAgI0ZseUJhbGxzDQogICAgVF9MRF9UX1BBID0gVF9MRC9UX1BBLCAgICNMaW5lRHJpdmVzDQogICAgVF9JRkZCX1RfUEEgPSBUX0lGRkIvVF9QQSwgICNJbmZpZWxkIEZseSBiYWxscw0KICAgIFRfUGl0Y2hlc19UX1BBPSBUX1BpdGNoZXMvVF9QQSwNCiAgICBUX0JhbGxzX1RfUEE9IFRfQmFsbHMvVF9QQSwNCiAgICBUX1N0cmlrZXNfVF9QQT0gVF9TdHJpa2VzL1RfUEEsDQogICAgVF9JRkhfVF9QQT0gVF9JRkgvVF9QQSwNCiAgICBUX0JVX1RfUEE9IFRfQlUvVF9QQSwNCiAgICBUX0JVSF9UX1BBPSBUX0JVSC9UX1BBLA0KICAgIFRfUEhfVF9QQT0gVF9QSC9UX1BBLA0KICAgIFRfQmFycmVsc19UX1BBPSBUX0JhcnJlbHMvVF9QQSwNCiAgICBUX0hhcmRIaXRzX1RfUEE9IFRfSGFyZEhpdC9UX1BBDQogICkgJT4lIHNlbGVjdCgtKFRfSDpUX0NTKSwtKFRfR0I6VF9CVUgpLC1UX1BILC1UX0JhcnJlbHMsLVRfSGFyZEhpdCwtVF9FdmVudHMpICNEcm9wIHRoZSBvbGQgdmFyaWFibGVzDQoNCg0KI3NraW0oRkRHX1RlYW01KSAlPiUgYXNfdGliYmxlKCkNCg0KDQpgYGANCg0KKioqICANCg0KIyMjIENyZWF0aW5nIExhZ2dlZCBWYXJpYWJsZXMgIA0KVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBsYWcgYSBkYXRhc2V0ICoqQlkgR1JPVVAqKi4gICAgDQoqIGBEcGx5cmAgd2F5IGlzIFtoZXJlLl0oaHR0cHM6Ly9zdGF0aXN0aWNzZ2xvYmUuY29tL2NyZWF0ZS1sYWdnZWQtdmFyaWFibGUtYnktZ3JvdXAtaW4tcikuICAgDQoqIFdoaWxlIGRhdGEudGFibGUgKHRoZSBtZXRob2QgdXNlZCBiZWxvdykgaXMgW2hlcmUuXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNjI5MTk4OC9ob3ctdG8tY3JlYXRlLWEtbGFnLXZhcmlhYmxlLXdpdGhpbi1lYWNoLWdyb3VwKSAgDQoNCmBgYHtyfQ0KI05vdGUgd2Ugd2lsbCBvbmx5IGJlIGxhZ2dpbmcgdGhlIHBsYXllciBsZXZlbCBkYXRhLCBhcyB0aGUgcHJldmlvdXMgeWVhcidzIHRlYW0gcGVyZm9ybWFuY2Ugc2hvdWxkbid0IGltcGFjdCBjdXJyZW50IHBlcmZvcm1hbmNlDQoNCg0KI09yZGVyIHRoZSBkYXRhc2V0IGJ5IGxhZyBjb2x1bW5zDQpwaXRjaGVyX2RhdGE1ID0gIGFycmFuZ2UocGl0Y2hlcl9kYXRhNCwgcGxheWVyaWQsU2Vhc29uKSAjcGxheWVyaWQgaXMgdGhlIEZhbmdyYXBoIGlkIGFzc2lnbmVkIHRvIGVhY2ggcGxheWVyDQoNCiMgQ29udmVydCBkYXRhZnJhbWUgdG8gZGF0YS50YWJsZSBmb3JtYXQNCkRUX3BpdGNoZXIgPSBkYXRhLnRhYmxlKHBpdGNoZXJfZGF0YTUpDQoNCiNkZXNpZ25hdGUgY29sdW1ucyB0byBsYWcgLSB3aGljaCBpcyBhbGwgb2YgdGhlbQ0KY29sczEgPSBjb2xuYW1lcyhwaXRjaGVyX2RhdGE1KQ0KYW5zY29scyA9IHBhc3RlKCJsYWciLCBjb2xzMSwgc2VwPSJfIikNCkRUX3BpdGNoZXJbLCAoYW5zY29scykgOj0gZGF0YS50YWJsZTo6c2hpZnQoLlNELCAxLCBOQSwgImxhZyIpLGJ5ID0ncGxheWVyaWQnLCAuU0Rjb2xzPWNvbHMxXSAjQ3JlYXRlIDEgcGVyaW9kIGxhZ3MgYnkgeWVhcg0KDQpwaXRjaGVyX2RhdGE2ID0gYXMuZGF0YS5mcmFtZShEVF9waXRjaGVyKSAlPiUgc2VsZWN0KC1sYWdfcGxheWVyaWQsIC1sYWdfVGVhbSwgLWxhZ19TZWFzb24sIC1sYWdfQWdlLC1sYWdfTmFtZSkNCg0KbmNvbChwaXRjaGVyX2RhdGE1KSAjMjUwIC0gbm8gbGFncw0KbmNvbChwaXRjaGVyX2RhdGE2KSAjNDk1IC0gbGFnZ2VkIGRhdGEgfiAoMjUwICogMiktNQ0KDQpgYGANCg0KKioqICAgIA0KDQojIyMgTWVyZ2luZyBUZWFtIGFuZCBQbGF5ZXIgRGF0YSAgDQpXZSBjYW4gdXNlIGVpdGhlciB0aGUgYG1lcmdlYCBmdW5jdGlvbiBvciB0aGUgU1FMIGZ1bmN0aW9uYWxpdHkgcHJvdmlkZWQgYnkgdGhlIGBzcWxkZmAgcGFja2FnZSB0byBqb2luIHRoZSBsYWdnZWQgcGxheWVyIGxldmVsIGRhdGEgdG8gdGhlIFRlYW0gbGV2ZWwgZGF0YQ0KDQpgYGB7cn0NCg0KZGZfcGl0Y2hpbmdfaW5pdCA9IHNxbGRmKA0KICAiDQogIHNlbGVjdCBhLiosIGIuKg0KICBmcm9tIHBpdGNoZXJfZGF0YTYgYQ0KICBsZWZ0IGpvaW4gRkRHX1RlYW01IGINCiAgb24gYS5UZWFtID0gYi5UX1RlYW0gYW5kIGEuU2Vhc29uID0gYi5UX1NlYXNvbg0KICANCiAgIg0KKSAgJT4lIHNlbGVjdCgtVF9UZWFtLC1UX1NlYXNvbiwtVF9BZ2UsLVRfRywtVF9BQikjIFVubmNlc3NhcnkgVGVhbSBWYXJpYWJsZXMNCg0KDQpucm93KGRmX3BpdGNoaW5nX2luaXQpIC0gbnJvdyhwaXRjaGVyX2RhdGE2KSAjY2hlY2sgaWYgYW55IHJvd3MgYXJlIGR1cGxpY2F0ZWQNCg0KDQpgYGANCg0KDQoqKiogIA0KDQoNCiMgQ3JlYXRpbmcgUmFua2luZ3MgZm9yIFBsYXllcnMgQmFzZWQgT24gUGVyY2VudGlsZXMgey50YWJzZXR9ICANCldlIGNhbiB1c2UgUGVyY2VudGlsZSBiYXNlZCByYW5raW5nIHRvIGdldCByYW5raW5ncyBmb3IgcGxheWVycyBmcm9tIHRoZSAyMDIxIHNlYXNvbi4gIA0KDQojIyBXb3J0aCBvZiBlYWNoIHN0YXQgIA0KDQojIyMgQ2FsY3VsYXRpbmcgcGFzdCBwZXJmb3JtYW5jZSAgIA0KDQpFYWNoIHBsYXllciBnb2VzIGZyb20gYSAwJSB0byAxMDAlIG9uIGVhY2ggcGVyY2VudGlsZSBzdGF0IHRoYXQgaXMgdXNlZCBmb3IgY3JlYXRpbmcgYSBzY29yaW5nIG9wcG9ydHVuaXR5LiBBbGwgZGF0YSBpcyBhbHJlYWR5IG5vcm1hbGl6ZWQgYnkgcGxhdGUgYXBwZWFyYW5jZXMsIGJ1dCBtdXN0IG5vdyBiZSByYW5rZWQgZm9yIGVhY2ggeWVhci4gICANCg0KYGBge3J9DQoNCiNDYXRlZ29yaWVzIEkgaW5jbHVkZSBhcmU6DQojV2lucywgU2F2ZXMsIFdISVAsIEVSQSwgU09zLCBIb2xkcw0KDQpkZl9waXRjaGluZ19pbml0MiA9ICBkZl9waXRjaGluZ19pbml0ICU+JQ0KIyAgYXJyYW5nZShwbGF5ZXJfaWQseWVhcikgJT4lIA0KICBncm91cF9ieShTZWFzb24pICU+JSANCiAgbXV0YXRlKA0KICAgIFdpbnNfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKFdfSVAsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgU09fc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKFNPX0lQLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIFNWX3NoYXJlID0gb3JkZXIob3JkZXIocmFuayhTVl9JUCx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgICBXSElQX3NoYXJlID0gb3JkZXIob3JkZXIocmFuayhXSElQLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIEVSQV9zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoRVJBLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgSExEX3NoYXJlID0gMCwNCiAgICBXb3J0aCA9IFdpbnNfc2hhcmUrU09fc2hhcmUrU1Zfc2hhcmUrV0hJUF9zaGFyZStFUkFfc2hhcmUrSExEX3NoYXJlDQogICAgKSAlPiUgDQogIHVuZ3JvdXAoKSANCmBgYA0KDQpDaGFydCBvZiB0aGUgRGlzdHJpYnV0aW9uIG9mIGluaXRpYWwgcGVyY2VudGlsZXMgIA0KQXMgdGhlIGNoYXJ0IGJlbG93IHNob3dzLCB0aGUgZGF0YSBpcyByb3VnaGx5IG5vcm1hbC4NCmBgYHtyfQ0KDQpza2V3bmVzcygoZGZfcGl0Y2hpbmdfaW5pdDIkV29ydGgpKQ0KDQpnZ3Bsb3QyOjpxcGxvdChkZl9waXRjaGluZ19pbml0MiRXb3J0aCwgbWFpbj0iVG90YWwgUGl0Y2hpbmcgV29ydGggRGF0YXNldCIpICsgZ2VvbV9oaXN0b2dyYW0oY29sb3VyPSJibGFjayIsIGZpbGw9ImdyZXkiKSArIHRoZW1lX2J3KCkNCg0KbWluKGRmX3BpdGNoaW5nX2luaXQyJFdvcnRoKQ0KDQptYXgoZGZfcGl0Y2hpbmdfaW5pdDIkV29ydGgpDQoNCmdncHVicjo6Z2dxcXBsb3QoZGZfcGl0Y2hpbmdfaW5pdDIkV29ydGgpDQoNCnNoYXBpcm8udGVzdChkZl9waXRjaGluZ19pbml0MiRXb3J0aCkNCmBgYA0KDQoNCioqKiAgDQoNCg0KIyMgMjAyMSBQbGF5ZXIgUmFua2luZ3MgLSBQZXIgSVAgcGVyZm9ybWFuY2UgDQojIyMgMjAyMSBQbGF5ZXIgUmFua2luZ3MgLSBUb3AgV29ydGggUGxheWVycw0KV2hpbGUgaXQgbG9va3MgbGlrZSBtYW55IG9mIHRoZSB0b3AgcGxheWVycyBoYXZlIGxvdyB3b3J0aCBzY29yZXMsIGl0IGlzIGJlY2F1c2Ugd2UgaGF2ZW4ndCBhcHBsaWVkIGEgbW9kaWZpZXIgZm9yIElQIHlldC4gV2lucyBhcmUgaGFyZGVyIHRvIGNvbWUgYnkgcmVsYXRpdmUgdG8gYW55IG90aGVyIHN0YXQgYW5kIHJlcXVpcmUgbW9yZSBpbm5pbmdzIHBpdGNoZWQuICAgICANCmBgYHtyLHdhcm5pbmc9RkFMU0V9DQoNCg0KZGZfcGl0Y2hpbmdfaW5pdDJfcmF3ID0gIGRmX3BpdGNoaW5nX2luaXQgJT4lDQojICBhcnJhbmdlKHBsYXllcl9pZCx5ZWFyKSAlPiUgDQogIGdyb3VwX2J5KFNlYXNvbikgJT4lIA0KICBtdXRhdGUoDQogICAgV2luc19zaGFyZV9yYXcgPSBvcmRlcihvcmRlcihyYW5rKFcsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgU09fc2hhcmVfcmF3ID0gb3JkZXIob3JkZXIocmFuayhTTyx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgICBTVl9zaGFyZV9yYXcgPSBvcmRlcihvcmRlcihyYW5rKFNWLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIFdISVBfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKFdISVAsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgRVJBX3NoYXJlID0gb3JkZXIob3JkZXIocmFuayhFUkEsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICBITERfc2hhcmVfcmF3ID0gMCwNCiAgICBXb3J0aCA9IFdpbnNfc2hhcmVfcmF3K1NPX3NoYXJlX3JhdytTVl9zaGFyZV9yYXcrV0hJUF9zaGFyZStFUkFfc2hhcmUrSExEX3NoYXJlX3Jhdw0KICAgICkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0Kc2VsZWN0KC1XLC1TTywtU1YsLVdISVAsLUVSQSwtSExEKQ0KDQoNCg0Kb3B0aW9ucyhkaWdpdHM9MikNCg0KZGZfcGl0Y2hpbmdfaW5pdDIwMjFfcmF3ID0NCmRmX3BpdGNoaW5nX2luaXQyX3JhdyAlPiUgDQogIGdyb3VwX2J5KE5hbWUpICU+JSANCiAgZmlsdGVyKFNlYXNvbiA9PSAyMDIxKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhXb3J0aCkpICU+JSANCiAgc2VsZWN0KE5hbWUsV2luc19zaGFyZV9yYXcsU09fc2hhcmVfcmF3LFNWX3NoYXJlX3JhdyxXSElQX3NoYXJlLEVSQV9zaGFyZSxITERfc2hhcmVfcmF3LFdvcnRoKQ0KDQoNCmRmX3BpdGNoaW5nX2luaXQyMDIxX3JhdyAlPiUNCiAgZmlsdGVyIChXb3J0aD4yLjkpICU+JSANCiAga2JsKCkgJT4lIA0KIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiLCJjb25kZW5zZWQiLCJyZXNwb25zaXZlIiksZnVsbF93aWR0aCA9IEYsZml4ZWRfdGhlYWQgPSBUKQ0KDQpgYGANCg0KDQoqKiogIA0KDQoNCg0KIyMgMjAyMSBQbGF5ZXIgUmFua2luZ3MgLSBBY3R1YWwgUGVyZm9ybWFuY2UgICAgDQojIyMgMjAyMSBQbGF5ZXIgUmFua2luZ3MgLSBUb3AgV29ydGggUGxheWVycyAgDQpUb3RhbCBSYW5raW5ncyBmb3IgdGhlIHBsYXllcnMgKFVzaW5nIDV4NSBTY29yaW5nKSBjYW4gYmUgZm91bmQgW2hlcmUuXSgpIFdoaWxlIGl0IGxvb2tzIGxpa2UgbWFueSBvZiB0aGUgdG9wIHBsYXllcnMgaGF2ZSBsb3cgd29ydGggc2NvcmVzLCBpdCBpcyBiZWNhdXNlIHdlIGhhdmVuJ3QgYXBwbGllZCBhIG1vZGlmaWVyIGZvciBJUCB5ZXQuICANCmBgYHtyLHdhcm5pbmc9RkFMU0V9DQoNCm9wdGlvbnMoZGlnaXRzPTIpDQoNCmRmX3BpdGNoaW5nX2luaXQyMDIxID0NCmRmX3BpdGNoaW5nX2luaXQyICU+JSANCiAgZ3JvdXBfYnkoTmFtZSkgJT4lIA0KICBmaWx0ZXIoU2Vhc29uID09IDIwMjEpICU+JSANCiAgYXJyYW5nZShkZXNjKFdvcnRoKSkgJT4lIA0KICBzZWxlY3QoTmFtZSxXaW5zX3NoYXJlLFNPX3NoYXJlLFNWX3NoYXJlLFdISVBfc2hhcmUsRVJBX3NoYXJlLEhMRF9zaGFyZSxXb3J0aCkNCg0KDQpkZl9waXRjaGluZ19pbml0MjAyMSAlPiUNCiAgZmlsdGVyIChXb3J0aD4zLjkpICU+JSANCiAga2JsKCkgJT4lIA0KIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiLCJjb25kZW5zZWQiLCJyZXNwb25zaXZlIiksZnVsbF93aWR0aCA9IEYsZml4ZWRfdGhlYWQgPSBUKQ0KDQpgYGANCg0KDQoqKiogIA0KDQoNCg0KIyBDcmVhdGluZyBNb2RlbCBGaWxlICAgDQojIyBBZGRpdGlvbmFsIERhdGEgUHJlcCAgDQojIyMgUmVtb3ZlIFZhcmlhYmxlcyB3aGljaCBhcmUgYmFzZWQgb2ZmIGN1cnJlbnQgaGl0dGluZyBudW1iZXJzICAgIA0KTm90IGFsbCB2YXJpYWJsZXMgY2FuIGJlIHVzZWQgZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcuICAgDQpgYGB7cn0NCmRmX3BpdGNoaW5nX2luaXQzID0gZGZfcGl0Y2hpbmdfaW5pdDINCiNCZSBjYXJlZnVsIGFib3V0IFJTIC0gUnVuIFN1cHBvcnQgYW5kIFJTLzkNCg0KICANCg0KDQpgYGANCg0KTGFnIFNoYXJlIFZhcmlhYmxlcyB0byB1c2UgZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcuIFRoZSB2YXJpYWJsZXMgdGhhdCB3ZSBjcmVhdGVkIGZvciB0aGUgV29ydGggbWV0cmljIG11c3QgYWxzbyBiZSByZW1vdmVkLiBUaGlzIHdpbGwgY3JlYXRlIHRoZSBmaW5hbCBkYXRhc2V0Lg0KDQpgYGB7cn0NCg0KI09yZGVyIHRoZSBkYXRhc2V0IGJ5IGxhZyBjb2x1bW5zDQpkZl9waXRjaGluZ19pbml0NCA9ICBhcnJhbmdlKGRmX3BpdGNoaW5nX2luaXQzLCBwbGF5ZXJpZCxTZWFzb24pICNwbGF5ZXJpZCBpcyB0aGUgRmFuZ3JhcGggaWQgYXNzaWduZWQgdG8gZWFjaCBwbGF5ZXINCg0KIyBDb252ZXJ0IGRhdGFmcmFtZSB0byBkYXRhLnRhYmxlIGZvcm1hdA0KRFRfcGl0Y2hlcjIgPSBkYXRhLnRhYmxlKGRmX3BpdGNoaW5nX2luaXQ0KQ0KDQojZGVzaWduYXRlIGNvbHVtbnMgdG8gbGFnIC0ganVzdCB0aGUgbmV3IHNoYXJlcw0KY29sczEgPSAoYygnV2luc19zaGFyZScsJ1NPX3NoYXJlJywnU1Zfc2hhcmUnLCAnRVJBX3NoYXJlJywnV0hJUF9zaGFyZScsJ0hMRF9zaGFyZScsJ1dvcnRoJykpDQphbnNjb2xzID0gcGFzdGUoImxhZyIsIGNvbHMxLCBzZXA9Il8iKSANCkRUX3BpdGNoZXIyWywgKGFuc2NvbHMpIDo9IGRhdGEudGFibGU6OnNoaWZ0KC5TRCwgMSwgTkEsICJsYWciKSxieSA9J3BsYXllcmlkJywgLlNEY29scz1jb2xzMV0gI0NyZWF0ZSAxIHBlcmlvZCBsYWdzIGJ5IHllYXINCg0KZGZfcGl0Y2hpbmdfZmluYWwgPSBhcy5kYXRhLmZyYW1lKERUX3BpdGNoZXIyKSAlPiUgDQogIHNlbGVjdCgtYyhXaW5zX3NoYXJlLFNPX3NoYXJlLFNWX3NoYXJlLCBFUkFfc2hhcmUsV0hJUF9zaGFyZSxITERfc2hhcmUsTmFtZSkpJT4lIA0Kc2VsZWN0KC1GSVAsLShSQVI6V1BBKSwtKHdGQjp3Q0gpLC0oYEVSQS1gOmB4RklQLWApLA0KICAgICAgIC1TSUVSQSwtKGBSQTktV0FSYDpgQWdlIFJuZ2ApLC1rd0VSQSwtKGB3Q0ggKHBpKWA6YHdTTCAocGkpYCksLShgSy85K2A6YEhSL0ZCJStgKSkgJT4lIHNlbGVjdCgtVywtU08sLVNWLC1ITEQsLVdfSVAsLVNPX0lQLC1TVl9JUCwtV0hJUCwtRVJBLC1ITERfSVApDQoNCmBgYA0KDQoNCg0KIyMjIENyZWF0aW5nIFRyYWluaW5nL1Rlc3QgU3BsaXQgIA0KV2Ugc3BsaXQgdGhlIGRhdGEgaW50byBUcmFpbmluZyBEYXRhICh3aGljaCBpcyB1c2VkIHRvIGNyZWF0ZSB0aGUgbW9kZWwpIGFuZCB0ZXN0IGRhdGEgKHdoaWNoIGlzIHVzZWQgdG8gdmFsaWRhdGUgdGhlIG1vZGVsKSAgIA0KYGBge3J9DQoNCnNldC5zZWVkKDE1Njc0KSAgIyBGb3IgcmVwcm9kdWNpYmlsaXR5DQojIENyZWF0ZSBpbmRleCBmb3IgdGVzdGluZyBhbmQgdHJhaW5pbmcgZGF0YQ0KaW5UcmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkZl9waXRjaGluZ19maW5hbCRXb3J0aCwgcCA9IDAuODAsIGxpc3QgPSBGQUxTRSkNCiMgc3Vic2V0IHBpdGNoaW5nIGRhdGEgZm9yIHRyYWluaW5nDQp0cl8yMDIxIDwtIGRmX3BpdGNoaW5nX2ZpbmFsW2luVHJhaW4sXQ0KIyBzdWJzZXQgdGhlIHJlc3QgdG8gdGVzdCBhbmQgdmFsaWRhdGUgdHJhaW5lZCBtb2RlbA0KdGVfMjAyMSA8LSBkZl9waXRjaGluZ19maW5hbFstaW5UcmFpbixdDQoNCm5yb3codHJfMjAyMSkvbnJvdyhkZl9waXRjaGluZ19maW5hbCkgI2NoZWNrIGlmIHNwbGl0IGlzIDAuOA0KDQpgYGANCg0KIyMjIFRyZWF0IE1pc3NpbmcgRGF0YSBieSBJbXB1dGluZyBNZWFuIFZhbHVlICANClZ0cmVhdCBQYWNrYWdlIGluIFIgaXMgZXhjZWxsZW50IGZvciB0cmVhdGluZyBkYXRhIGJlZm9yZSB1c2luZyBmb3IgbW9kZWxpbmcuIEFkZGl0aW9uYWwgZG9jdW1lbnRhdGlvbiBjYW4gYmUgZm91bmQgW2hlcmUuXShodHRwczovL3dpbnZlY3Rvci5naXRodWIuaW8vdnRyZWF0L2luZGV4Lmh0bWwpDQpgYGB7cn0NCnRyZWF0X3BsYW5fMjAyMSA8LSB2dHJlYXQ6OmRlc2lnblRyZWF0bWVudHNaKA0KICBkZnJhbWUgPSB0cl8yMDIxLCAjIHRyYWluaW5nIGRhdGENCiAgdmFybGlzdCA9IGNvbG5hbWVzKHRyXzIwMjEpICU+JSAuWy4gIT0gImhpdHRpbmdfc2NvcmUxIl0sICMgaW5wdXQgdmFyaWFibGVzID0gYWxsIHRyYWluaW5nIGRhdGEgY29sdW1ucywgZXhjZXB0IHJhbmRvbQ0KICBjb2RlUmVzdHJpY3Rpb24gPSBjKCJjbGVhbiIsICJpc0JBRCIsICJsZXYiKSwgIyBkZXJpdmVkIHZhcmlhYmxlcyB0eXBlcyAoZHJvcCBjYXRfUCkNCiAgdmVyYm9zZSA9IEZBTFNFKSAjIHN1cHByZXNzIG1lc3NhZ2VzDQoNCiNjbGVhbiBzdGFuZHMgZm9yIGNsZWFuZWQgbnVtZXJpY2FsIHZhcmlhYmxlLCBpc0JBRCBpbmRpY2F0ZXMgdGhhdCBhIHZhbHVlIHJlcGxhY2VtZW50IGhhcyBvY2N1cnJlZCAod2hpY2ggaW5kaWNhdGVzIGEgbWlzc2luZyB2YWx1ZSBpbiB0aGlzIGNhc2UpLCBhbmQgbGV2IGlzIGEgYmluYXJ5IGluZGljYXRvciB3aGV0aGVyIGEgcGFydGljdWxhciB2YWx1ZSBvZiB0aGF0IGNhdGVnb3JpY2FsIHZhcmlhYmxlIHdhcyBwcmVzZW50LiAgDQoNCiMjIyMgQ2hlY2tpbmcgU2NvcmVmcmFtZQ0KDQpzY29yZV9mcmFtZSA8LSB0cmVhdF9wbGFuXzIwMjEkc2NvcmVGcmFtZSAlPiUgDQogIHNlbGVjdCh2YXJOYW1lLCBvcmlnTmFtZSwgY29kZSkNCg0KaGVhZChzY29yZV9mcmFtZSkNCg0KDQp0cl90cmVhdGVkXzIwMjEgPC0gdnRyZWF0OjpwcmVwYXJlKHRyZWF0X3BsYW5fMjAyMSwgdHJfMjAyMSkNCnRlX3RyZWF0ZWRfMjAyMSA8LSB2dHJlYXQ6OnByZXBhcmUodHJlYXRfcGxhbl8yMDIxLCB0ZV8yMDIxKQ0KdG90YWxfdHJlYXRlZF8yMDIxX3BpdGNoaW5nIDwtIHZ0cmVhdDo6cHJlcGFyZSh0cmVhdF9wbGFuXzIwMjEsIGFzLmRhdGEuZnJhbWUoRFRfcGl0Y2hlcjIpKQ0KDQojdHJfdHJlYXRlZCA9IHRyDQojdGVfdHJlYXRlZCA9IHRlDQoNCmRpbSh0cl90cmVhdGVkXzIwMjEpICNub3RlIHRoZXJlIGFyZSBkdW1taWVzIGZvciBlYWNoIHBsYXllciBhbmQgdGVhbQ0KDQpgYGANCg0KDQoqKiogICAgDQoNCg0KIyMjIENoZWNrIERpc3RyaWJ1dGlvbiBvZiBUcmFpbmluZyBQb3B1bGF0aW9uICANClRoZSBwb3B1bGF0aW9uIHVzZWQgZm9yIFRyYWluaW5nIHNob3VsZCBiZSBpbmRpY2F0aXZlIG9mIFRvdGFsIFBvcHVsYXRpb24NCmBgYHtyfQ0KDQpnZ3Bsb3QyOjpxcGxvdCh0cl90cmVhdGVkXzIwMjEkV29ydGgsIG1haW49IlRyYWluaW5nIFNldCIpICsgZ2VvbV9oaXN0b2dyYW0oY29sb3VyPSJibGFjayIsIGZpbGw9ImdyZXkiKSArIHRoZW1lX2J3KCkNCg0Kc2tld25lc3ModHJfdHJlYXRlZF8yMDIxJFdvcnRoKSAjVGhlIHNrZXduZXNzIGlzIHRoZSBzYW1lIGFzIHRoZSBvdmVyYWxsDQoNCg0KYGBgDQoNCg0KKioqICAgIA0KDQoNCg0KIyBSdW5uaW5nIFhHYm9vc3QgTW9kZWwgey50YWJzZXR9IA0KVG8ga2VlcCB0aGluZ3Mgc2ltcGxlIHdpdGggbW9kZWxpbmcsIHdl4oCZbGwgdHVybiB0aGUgdHJhaW5pbmcgZGF0YSBpbnRvIHNpbXBsZSBpbnB1dCB2YXJpYWJsZXMgZm9yIGBjYXJldDo6dHJhaW5gLCBkcm9wcGluZyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgYW5kIGNvbnZlcnRpbmcgdGhlIGRhdGEgZnJhbWUgdG8gYSBtYXRyaXguIERvY3VtZW50YXRpb24gZm9yIHRoaXMgYXBwcm9hY2ggdG8gWEdib29zdCBjYW4gYmUgZm91bmQgW2hlcmUuXShodHRwczovL3d3dy5rYWdnbGUuY29tL3BlbGtvamEvdmlzdWFsLXhnYm9vc3QtdHVuaW5nLXdpdGgtY2FyZXQpICAgIA0KDQojIyBUdW5pbmcgdGhlIE1vZGVsDQoNCiMjIyBJbml0aWFsIE5vbi1UdW5lZCBNb2RlbA0KQnJlYWsgdGhlIGRhdGEgc2V0IGludG8geCBhbmQgeSBpbnB1dHMgd2l0aCB4IGJlaW5nIGEgbWF0cml4ICANCmBgYHtyfQ0KaW5wdXRfeCA8LSBhcy5tYXRyaXgoKCh0cl90cmVhdGVkXzIwMjEpKSU+JQ0KICAgc2VsZWN0KC1Xb3J0aCkgJT4lICAgICAgICAgICAgICAgICAgICAgIA0KICAgc2VsZWN0KCFlbmRzX3dpdGggKCJfaXNCQUQiKSkpDQoNCmlucHV0X3kgPC0gdHJfdHJlYXRlZF8yMDIxJFdvcnRoDQoNCmBgYA0KDQpYR0Jvb3N0IHdpdGggRGVmYXVsdCBIeXBlcnBhcmFtZXRlcnMgICAgDQpUaGUgVmFyaWFibGUgSW1wb3J0YW5jZSAoYGNhcmV0Ojp2YXJJbXAoeGdiX2Jhc2VfMjAyMSwgc2NhbGUgPSBGICApYCkgZnJvbSB0aGUgY2FyZXQgcGFja2FnZSBzaG93cyB0aGUgY29udHJpYnV0aW9uIG9mIGVhY2ggdmFyaWFibGUgdG8gdGhlIGluaXRpYWwgbW9kZWwuIEFzIHlvdSBjYW4gc2VlIFNMR19wbHVzXyAoU0xHKykgdGFrZXMgdXAgbXVjaCBvZiB0aGUgaW1wb3J0YW5jZSBhcyBpdCBpcyBkZXJpdmVkIGZyb20gU0xHIChvbmUgb2YgdGhlIGtleSBjb250cmlidXRvcnMgdG8gV29ydGgpLiBUaGVzZSB0eXBlcyBvZiB2YXJpYWJsZXMgd2lsbCBiZSByZW1vdmVkIGR1cmluZyB2YXJpYWJsZSBzZWxlY3Rpb24gaW4gdGhlIG5leHQgc3RlcC4gIA0KKlhHQm9vc3QgZG9jdW1lbnRhdGlvbiBjYW4gYmUgZm91bmQgZm9yIG1vcmUgZ2VuZXJhbCBtb2RlbHMgW2hlcmUuXShodHRwczovL3d3dy5rYWdnbGUuY29tL2NvZGUvcnRhdG1hbi9tYWNoaW5lLWxlYXJuaW5nLXdpdGgteGdib29zdC1pbi1yL25vdGVib29rKSoNCg0KYGBge3J9DQoNCiNEZWZhdWx0cyBmb3IgeGdib29zdCBtb2RlbA0KZ3JpZF9kZWZhdWx0IDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gMTAwLA0KICBtYXhfZGVwdGggPSA2LA0KICBldGEgPSAwLjMsDQogIGdhbW1hID0gMCwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IDEsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSAxLA0KICBzdWJzYW1wbGUgPSAxDQopDQoNCiNUaGlzIGlzIGEgYmxhbmsgdHJhaW5fY29udHJvbCBzZXQsIHRoaXMgd2lsbCBiZSB1cGRhdGVkIGFmdGVyDQp0cmFpbl9jb250cm9sIDwtIGNhcmV0Ojp0cmFpbkNvbnRyb2woDQogIG1ldGhvZCA9ICJub25lIiwNCiAgdmVyYm9zZUl0ZXIgPSBGQUxTRSwgIyBubyB0cmFpbmluZyBsb2cNCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUgIyBGQUxTRSBmb3IgcmVwcm9kdWNpYmxlIHJlc3VsdHMgDQopDQoNCnhnYl9iYXNlXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gZ3JpZF9kZWZhdWx0LA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCmNhcmV0Ojp2YXJJbXAoeGdiX2Jhc2VfMjAyMSwgc2NhbGUgPSBGICApDQoNCg0KDQoNCmBgYA0KDQoNCioqKiAgICANCg0KDQojIyBGdXJ0aGVyIFZhcmlhYmxlIFNlbGVjdGlvbiAgDQojIyMgUmVtb3ZlIHJlZHVuZGFudCBhbmQgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzICANCg0KDQpTZWxlY3Rpb24gUmVtb3ZhbCBTdGVwIDE6IENoZWNrIGZvciBoaWdoIGNvcnJlbGF0aW9ucyAgDQpOb3JtYWxseSwgdGhpcyBzdGVwIGlzIGRvbmUgZWFybHksIGJ1dCB0aG9zZSBzdGVwcyB3ZXJlIHJlc2VydmVkIGZvciBwcmVwYXJpbmcgdGhlIGRhdGEgIA0KDQpgYGB7cn0NCg0KZGVwX2NvcjEgPC0gdChhcy5kYXRhLmZyYW1lKGNvcih0cl90cmVhdGVkXzIwMjFbICwgY29sbmFtZXModHJfdHJlYXRlZF8yMDIxKSAhPSAiV29ydGgiXSwNCiAgICAgICAgICAgICAgICB0cl90cmVhdGVkXzIwMjEkV29ydGgpKSkNCmRlcF9jb3IxIDwtDQphcy5kYXRhLmZyYW1lKHQoYXMuZGF0YS5mcmFtZShkZXBfY29yMSklPiUgDQogIHNlbGVjdCghc3RhcnRzX3dpdGgoImxhZyIpKSAlPiUgI3JlbW92ZSBsYWcgdmFyaWFibGVzDQogIHNlbGVjdCghY29udGFpbnMoIl9pc0JBRCIpKSkpIA0KDQpkZXBfY29yMSA8LSB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbihkZXBfY29yMSwiVkFSSUFCTEVTIiklPiUgI3JlbW92ZSBpbmRpY2F0b3JzIGZvciBtaXNzaW5nIGRhdGENCiAgZmlsdGVyKFYxID4gMC40MHxWMSA8IC0wLjMpDQoNCmRlcF9jb3IxDQoNCmRlcF9jb3IyIDwtIGNvbG5hbWVzKHJvd190b19uYW1lcyh0KGRlcF9jb3IxKSxyb3dfbnVtYmVyID0gMSkpDQoNCg0KDQpgYGANCkxldCdzIFJlbW92ZSB2YXJpYWJsZXMgd2l0aCBoaWdoIGNvcnJlbGF0aW9uIHRvIHdvcnRoIG1ldHJpYywgYW5kIG1ldHJpY3MgdGhhdCBhcmUgY2FsY3VsYXRlZCBhZnRlciBhIHBsYXllcidzIHBlcmZvcm1hbmNlIChzdWNoIGFzIFdBUikNCg0KYGBge3J9DQoNCmlucHV0X3ggPC0gYXMubWF0cml4KCgodHJfdHJlYXRlZF8yMDIxKSklPiUNCiAgIHNlbGVjdCgtV29ydGgpICU+JSAjUmVtb3ZlIHNvbWUgdmFyaWFibGVzIHZhcmlhYmxlcw0KICAgICBzZWxlY3QgKC1SU19JUCwtRVJfSVAsLVJfSVAsLVJFVywtUkUyNCwtQ2x1dGNoLC1XUEFfc2xhc2hfTEksLVNlYXNvbiAjUmVtb3ZlIHJlZHVuZGFudCB2YXJpYWJsZXMgb3Igbm9uL3dlaWdodGVkIHZhcmlhYmxlcw0KKSAlPiUgICAgICANCnNlbGVjdCghZW5kc193aXRoICgiX2lzQkFEIikpKSAjaW5kaWNhdG9yIHZhcmlhYmxlIGZvciBtaXNzaW5nIGRhdGENCg0KaW5wdXRfeSA8LSB0cl90cmVhdGVkXzIwMjEkV29ydGgNCg0KDQoNCg0KDQpgYGANCg0KUnVuIHRoZSBtb2RlbCBvbiB0aGUgbmV3IGRhdGFzZXQgdG8gbWFrZSBzdXJlIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlcyBsb29rIGZpbmUNCmBgYHtyfQ0KDQojTm90ZSBUcmFpbmluZyBwYXJhbWV0ZXJzIHdlcmUgc2V0IGluIGluaXRpYWwgbW9kZWwgc2V0IHVwDQp4Z2JfYmFzZV8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICB0dW5lR3JpZCA9IGdyaWRfZGVmYXVsdCwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQpjYXJldDo6dmFySW1wKHhnYl9iYXNlXzIwMjEsIHNjYWxlID0gRiAgKQ0KDQoNCmBgYA0KDQoNCioqKiAgICANCg0KDQoNCiMjIE1vZGVsIHdpdGggbmV3IGRhdGEgIA0KDQojIyMgVHVuaW5nIEFsbCBIeXBlcnBhcmFtZXRlcnMNCkEgdHVuZSBncmlkIGFsbG93cyB1cyB0byB0ZXN0IGEgbGFyZ2UgYW1vdW50IG9mIGh5cGVyLXBhcmFtZXRlcnMgYW5kIGZpbmQgdGhlIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBSTVNFIGZvciBwcmVkaWN0aW9ucy4gICANCkhvd2V2ZXIsIFRoZSBtb3JlIHZhbHVlcyB5b3Ugd2FudCB0byB0ZXN0IGFuZCB0aGUgZ3JlYXRlciB0aGUgYW1vdW50IG9mIENyb3NzLUZvbGQgVmFsaWRhdGlvbnMgKGBtZXRob2QgPSAiY3YiYCksIHRoZSBncmVhdGVyIHRoZSBjb21wdXRhdGlvbmFsIHRpbWUgaXQgd2lsbCB0YWtlLiBNb3JlIGluZm9ybWF0aW9uIG9uIHRoZSBzcGVjaWZpYyBwYXJhbWV0ZXJzIGNhbiBiZSBmb3VuZCBbaGVyZS5dKGh0dHBzOi8vd3d3LmhhY2tlcmVhcnRoLmNvbS9wcmFjdGljZS9tYWNoaW5lLWxlYXJuaW5nL21hY2hpbmUtbGVhcm5pbmctYWxnb3JpdGhtcy9iZWdpbm5lcnMtdHV0b3JpYWwtb24teGdib29zdC1wYXJhbWV0ZXItdHVuaW5nLXIvdHV0b3JpYWwvKQ0KDQpgYGB7cn0NCg0KIyBtYXhpbXVtIG51bWJlciBvZiB0cmVlcw0KbnJvdW5kcyA8LSAxMDAwDQoNCiMgbm90ZSB0byBzdGFydCBucm91bmRzIGZyb20gMjAwLCBhcyBzbWFsbGVyIGxlYXJuaW5nIHJhdGVzIHJlc3VsdCBpbiBlcnJvcnMgc28NCiMgYmlnIHdpdGggbG93ZXIgc3RhcnRpbmcgcG9pbnRzIHRoYXQgdGhleSdsbCBtZXNzIHRoZSBzY2FsZXMNCnR1bmVfZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gMTAwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSBjKDAuMDEsIDAuMDI1LCAwLjA1LCAwLjA3NSwgMC4xKSwNCiAgbWF4X2RlcHRoID0gYygyLCA0LCA2LCA4LCAxMCksDQogIGdhbW1hID0gMCwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IDEsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSAxLA0KICBzdWJzYW1wbGUgPSAxDQopDQoNCnR1bmVfY29udHJvbCA8LSBjYXJldDo6dHJhaW5Db250cm9sKA0KICBtZXRob2QgPSAiY3YiLCAjIGNyb3NzLXZhbGlkYXRpb24NCiAgbnVtYmVyID0gNSwgIyB3aXRoIG4gZm9sZHMgDQogICMjIE5vdGUgdGhpcyB3YXMgIyBvdXQgaW4gdGhlIG9yaWdpbmFsIGNvZGUNCiAgI2luZGV4ID0gY3JlYXRlRm9sZHModHJfdHJlYXRlZCRJZF9jbGVhbiksICMgZml4IHRoZSBmb2xkcw0KICB2ZXJib3NlSXRlciA9IEZBTFNFLCAjIG5vIHRyYWluaW5nIGxvZw0KICBhbGxvd1BhcmFsbGVsID0gVFJVRSAjIEZBTFNFIGZvciByZXByb2R1Y2libGUgcmVzdWx0cyANCikNCg0KDQoNCmBgYA0KDQoqUnVubmluZyB0aGUgaW5pdGlhbCB0dW5pbmcgbW9kZWwqICANCmBgYHtyfQ0KI05vdGUgSSB3aWxsIGJlIHRpbWluZyB0aGVzZSBydW5zIHRvIGdpdmUgYW4gZXN0aW1hdGUgb24gaG93IGxvbmcgdGhpcyBtb2RlbCB0YWtlcyB0byBydW4NCnN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQp4Z2JfdHVuZV8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0dW5lX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBGQUxTRQ0KICAsdmVyYm9zaXR5ID0gMA0KKQ0KDQplbmRfdGltZSA8LSBTeXMudGltZSgpDQoNCmVuZF90aW1lIC0gc3RhcnRfdGltZQ0KDQpgYGANCg0KKlR1bmluZyBQbG90IGFuZCBWYXJpYWJsZSBJbXBvcnRhbmNlKg0KYGBge3J9DQp2YXJJbXAoeGdiX3R1bmVfMjAyMSwgc2NhbGUgPSBGICApIA0KDQoNCiMgaGVscGVyIGZ1bmN0aW9uIGZvciB0aGUgcGxvdHMNCnR1bmVwbG90IDwtIGZ1bmN0aW9uKHgsIHByb2JzID0gLjkwKSB7DQogIGdncGxvdCh4KSArDQogICAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKHF1YW50aWxlKHgkcmVzdWx0cyRSTVNFLCBwcm9icyA9IHByb2JzKSwgbWluKHgkcmVzdWx0cyRSTVNFKSkpICsNCiAgICB0aGVtZV9idygpDQp9DQoNCnR1bmVwbG90KHhnYl90dW5lXzIwMjEpDQpgYGANCg0KDQoqKiogICAgDQoNCg0KIyMjIEZpbmUgVHVuaW5nIE1vZGVsICANCiMjIyMgU2Vjb25kIFR1bmluZzogTWF4aW11bSBEZXB0aCBhbmQgTWluaW11bSBDaGlsZCBXZWlnaHQgIA0KQWZ0ZXIgZml4aW5nIHRoZSBsZWFybmluZyByYXRlIHRvIDAuMSBhbmQgd2XigJlsbCBhbHNvIHNldCBtYXhpbXVtIGRlcHRoIHRvIDMgKy0xIChvciArMiBpZiBtYXhfZGVwdGggPT0gMikgdG8gZXhwZXJpbWVudCBhIGJpdCBhcm91bmQgdGhlIHN1Z2dlc3RlZCBiZXN0IHR1bmUgaW4gcHJldmlvdXMgc3RlcC4gVGhlbiwgd2VsbCBmaXggbWF4aW11bSBkZXB0aCBhbmQgbWluaW11bSBjaGlsZCB3ZWlnaA0KDQpgYGB7cn0NCnR1bmVfZ3JpZDIgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSBzZXEoZnJvbSA9IDUwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSB4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJGV0YSwNCiAgbWF4X2RlcHRoID0gaWZlbHNlKHhnYl90dW5lXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoID09IDIsDQogICAgYyh4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aDo0KSwNCiAgICB4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCAtIDE6eGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGggKyAxKSwNCiAgZ2FtbWEgPSAwLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gMSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMiwgMyksDQogIHN1YnNhbXBsZSA9IDENCikNCg0KeGdiX3R1bmUyXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQyLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCnR1bmVwbG90KHhnYl90dW5lMl8yMDIxKQ0KDQp4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZQ0KDQp2YXJJbXAoeGdiX3R1bmUyXzIwMjEsIHNjYWxlID0gRiAgKSANCmBgYA0KDQoNCioqKiAgICANCg0KDQojIyMjIFRoaXJkIFR1bmluZzogQ29sdW1uIGFuZCBSb3cgU2FtcGxpbmcNCg0KYGBge3J9DQoNCnR1bmVfZ3JpZDMgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSBzZXEoZnJvbSA9IDUwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSB4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJGV0YSwNCiAgbWF4X2RlcHRoID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoLA0KICBnYW1tYSA9IDAsDQogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNCwgMC42LCAwLjgsIDEuMCksDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSBjKDAuNSwgMC43NSwgMS4wKQ0KKQ0KDQp4Z2JfdHVuZTNfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHVuZV9jb250cm9sLA0KICB0dW5lR3JpZCA9IHR1bmVfZ3JpZDMsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikNCg0KdHVuZXBsb3QoeGdiX3R1bmUzXzIwMjEsIHByb2JzID0gLjk1KQ0KDQp4Z2JfdHVuZTNfMjAyMSRiZXN0VHVuZQ0KDQp2YXJJbXAoeGdiX3R1bmUzXzIwMjEsIHNjYWxlID0gRiAgKSANCg0KYGBgDQoNCg0KKioqICAgIA0KDQoNCiMjIyMgRm91cnRoIFR1bmluZzogR2FtbWEgIA0KTmV4dCwgd2UgYWdhaW4gcGljayB0aGUgYmVzdCB2YWx1ZXMgZnJvbSBwcmV2aW91cyBzdGVwLCBhbmQgbm93IHdpbGwgc2VlIHdoZXRoZXIgY2hhbmdpbmcgdGhlIGdhbW1hIGhhcyBhbnkgZWZmZWN0IG9uIHRoZSBtb2RlbCBmaXQ6DQpgYGB7cn0NCnR1bmVfZ3JpZDQgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSBzZXEoZnJvbSA9IDUwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSB4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJGV0YSwNCiAgbWF4X2RlcHRoID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoLA0KICBnYW1tYSA9IGMoMCwgMC4wNSwwLjEsIDAuMiwwLjQsIDAuNSwgMC43LCAwLjksIDEuMCksDQogIGNvbHNhbXBsZV9ieXRyZWUgPSB4Z2JfdHVuZTNfMjAyMSRiZXN0VHVuZSRjb2xzYW1wbGVfYnl0cmVlLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWluX2NoaWxkX3dlaWdodCwNCiAgc3Vic2FtcGxlID0geGdiX3R1bmUzXzIwMjEkYmVzdFR1bmUkc3Vic2FtcGxlDQopDQoNCnhnYl90dW5lNF8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0dW5lX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkNCwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQp0dW5lcGxvdCh4Z2JfdHVuZTRfMjAyMSkNCg0KeGdiX3R1bmU0XzIwMjEkYmVzdFR1bmUNCg0KdmFySW1wKHhnYl90dW5lNF8yMDIxLCBzY2FsZSA9IEYgICkgDQpgYGANCg0KDQoqKiogICAgDQoNCg0KIyMjIyBGaWZ0aCBUdW5pbmc6IFJlZHVjaW5nIHRoZSBMZWFybmluZyBSYXRlICANCk5vdywgd2UgaGF2ZSB0dW5lZCB0aGUgaHlwZXJwYXJhbWV0ZXJzIGFuZCBjYW4gc3RhcnQgcmVkdWNpbmcgdGhlIGxlYXJuaW5nIHJhdGUgdG8gZ2V0IHRvIHRoZSBmaW5hbCBtb2RlbDogIA0KDQpgYGB7cn0NCnN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQp0dW5lX2dyaWQ1IDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gc2VxKGZyb20gPSAxMDAsIHRvID0gMTAwMDAsIGJ5ID0gNzUpLA0KICAgZXRhID0gYygwLjAxLCAwLjAxNSwgMC4wMjUsMC4wMzUsIDAuMDUsMC43NSwgMC4xKSwNCiAgbWF4X2RlcHRoID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoLA0KICBnYW1tYSA9IHhnYl90dW5lNF8yMDIxJGJlc3RUdW5lJGdhbW1hLA0KICBjb2xzYW1wbGVfYnl0cmVlID0geGdiX3R1bmUzXzIwMjEkYmVzdFR1bmUkY29sc2FtcGxlX2J5dHJlZSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1pbl9jaGlsZF93ZWlnaHQsDQogIHN1YnNhbXBsZSA9IHhnYl90dW5lM18yMDIxJGJlc3RUdW5lJHN1YnNhbXBsZQ0KKQ0KDQoNCg0KeGdiX3R1bmU1XzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQ1LA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCiN0dW5lcGxvdCh4Z2JfdHVuZTVfMjAyMSkNCg0KZW5kX3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQplbmRfdGltZSAtIHN0YXJ0X3RpbWUNCg0KeGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUNCg0KdmFySW1wKHhnYl90dW5lNV8yMDIxLCBzY2FsZSA9IEYgICkgDQpgYGANCg0KDQoqKiogICAgDQoNCg0KDQojIyMjIEZpdHRpbmcgRmluYWwgTW9kZWwNCg0KYGBge3J9DQoNCihmaW5hbF9ncmlkXzIwMjEgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRucm91bmRzLA0KICBldGEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikpDQoNCih4Z2JfbW9kZWxfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgdHVuZUdyaWQgPSBmaW5hbF9ncmlkXzIwMjEsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikpDQoNCnZhckltcCh4Z2JfbW9kZWxfMjAyMSwgc2NhbGUgPSBGICApIA0KDQpgYGANCg0KDQoqKiogICAgDQoNCg0KIyMgTW9kZWwgUGVyZm9ybWFuY2UgIA0KDQoNCiMjIyBDaGVja2luZyBNb2RlbCBvbiBUZXN0IFNwbGl0IERhdGEgIA0KV2UgZG9uJ3QgbmVlZCB0byBsb29rIHRvbyBjbG9zZWx5IGF0IGFyZSB0cmFpbmluZyBkYXRhIGFzIFhnYm9vc3Qgd2lsbCBoZWF2aWx5IG92ZXJmaXQgdGhlIG1vZGVsIGJhc2VkIG9uIHRoYXQgZGF0YS4gVGhlIG1vcmUgaW1wb3J0YW50IHBhcnQgaXMgaG93IHRoZSBtb2RlbCBwZXJmb3JtcyBvbiBpbiBwcmVkaWN0aW5nIG91ciBUZXN0IFNhbXBsZSB0aGF0IHdhcyBub3QgaW5jbHVkZWQuICANCg0KYGBge3J9DQoNCg0KeV9wcmVkX3Rlc3QgPC0gcHJlZGljdCh4Z2JfbW9kZWxfMjAyMSwgZGF0YS5tYXRyaXgodGVfdHJlYXRlZF8yMDIxKSkNCg0KdGVzdF9zdGF0cz0gY2JpbmQoKHRlX3RyZWF0ZWRfMjAyMSRXb3J0aCkseV9wcmVkX3Rlc3QpDQoNCnRlc3Rfc3RhdHNSMiA9IGNvcih0ZXN0X3N0YXRzWywxXSx0ZXN0X3N0YXRzWywyXSleMg0KDQpwcmludCh0ZXN0X3N0YXRzUjIpDQoNCg0KeV9wcmVkX3RyYWluIDwtIHByZWRpY3QoeGdiX21vZGVsXzIwMjEsIGRhdGEubWF0cml4KHRyX3RyZWF0ZWRfMjAyMSkpDQoNCnRyYWluX3N0YXRzID0gY2JpbmQoKHRyX3RyZWF0ZWRfMjAyMSRXb3J0aCkseV9wcmVkX3RyYWluKQ0KDQp0cmFpbl9zdGF0c1IyID0gY29yKHRyYWluX3N0YXRzWywxXSx0cmFpbl9zdGF0c1ssMl0pXjINCg0KcHJpbnQodHJhaW5fc3RhdHNSMikNCg0KI3Rlc3QgZGF0YXNldA0KeCA8LSBzZWxlY3QodGVfdHJlYXRlZF8yMDIxLCAtV29ydGgpDQp5IDwtICh0ZV90cmVhdGVkXzIwMjEkV29ydGgpDQoNCih4Z2JfbW9kZWxfcm1zZSA8LSBNb2RlbE1ldHJpY3M6OnJtc2UoeSwgcHJlZGljdCh4Z2JfbW9kZWxfMjAyMSwgbmV3ZGF0YSA9IHgpKSkNCg0KaG9sZG91dF94IDwtIHNlbGVjdCh0cl90cmVhdGVkXzIwMjEsIC1Xb3J0aCkNCmhvbGRvdXRfeSA8LSB0cl90cmVhdGVkXzIwMjEkV29ydGgNCg0KKHhnYl9tb2RlbF9ybXNlIDwtIE1vZGVsTWV0cmljczo6cm1zZShob2xkb3V0X3ksIHByZWRpY3QoeGdiX21vZGVsXzIwMjEsIG5ld2RhdGEgPSBob2xkb3V0X3gpKSkNCg0KDQpgYGANCg0KIyMjIyBHcmFwaGljYWwgUmVwcmVzZW50YXRpb24gb2YgTW9kZWwgICANCg0KDQpgYGB7cn0NCg0KZ2dwbG90Mjo6Z2dwbG90KCkgKw0KICBhZXMoeCA9IHRlc3Rfc3RhdHNbLDFdLCB5ID0gdGVzdF9zdGF0c1ssMl0pICsNCiAgZ2VvbV9qaXR0ZXIoKSArDQogIHhsYWIoIlByZWRpY3RlZCBWYWx1ZXMiKSArDQogIHlsYWIoIkFjdHVhbCBWYWx1ZXMiKSArDQogIGdndGl0bGUoIlJlc3VsdHMgb2YgUGl0Y2hpbmcgTW9kZWwgb24gVGVzdCBEYXRhIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsc2l6ZSA9IDIyLGNvbG9yID0ic3RlZWwgYmx1ZSIpKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikNCg0KDQoNCmBgYA0KDQoNCioqKiAgICANCg0KDQoNCiMgQ3JlYXRpbmcgMjAyMiBQcm9qZWN0aW9ucyBmcm9tIE1vZGVsICB7LnRhYnNldH0gDQoNCg0KIyMgUmUtZml0IG1vZGVsIGZvciBJbXBvcnRhbnQgVmFyaWFibGVzDQpOb3cgdGhhdCB3ZSBoYXZlIGFuIGFjY2VwdGFibGUgbW9kZWwsIHdlIGNhbiB1c2UgaXQgdG8gY3JlYXRlIHByb2plY3Rpb25zIGZvciBob3cgd2VsbCB3ZSB0aGluayBwbGF5ZXJzIHNob3VsZCBkbyBpbiAyMDIyIGJhc2VkIG9uIHRoZWlyIGhpdHRpbmcgc3RhdGlzdGljcyBpbiAyMDIxLiBGaXJzdCBsZXQncyByZWR1Y2UNCg0KMS4gT25seSBrZWVwIHZhcmlhYmxlcyB3aXRoIGhpZ2ggZW5vdWdoIGltcG9ydGFuY2UgaW4gbW9kZWwgIA0KDQpgYGB7cn0NCg0KDQp2aXAoeGdiX21vZGVsXzIwMjEsIG51bV9mZWF0dXJlcyA9IDMwKSAgIyAxMCBpcyB0aGUgZGVmYXVsdCwgMzAgZ2l2ZXMgYSB2aXN1YWwgb24gdGhlIHRvcCAzMCBtb3N0IGltcG9ydGFudCBmZWF0dXJlcyBvZiB0aGUgbW9kZWwNCg0KdW5zY2FsZXZpID0gdmkoeGdiX21vZGVsXzIwMjEsIG1ldGhvZD0ibW9kZWwiKSAjc2hvd3MgdGhlIG51bWJlcnMgYmVoaW5kIHRoZSBwbG90DQoNCnVuc2NhbGV2aSRJbXBvcnRhbmNlX3BlcmMgPSB3aXRoKHVuc2NhbGV2aSxJbXBvcnRhbmNlL3N1bShJbXBvcnRhbmNlKSkgI2FkZHMgcGVyY2VudGFnZXMgDQoNCnVuc2NhbGV2aSAjIGltcG9ydGFuY2UgYnkgdmFyaWFibGVzDQoNCnZhcmlhYmxlc190b19rZWVwXzIwMjEgPSBzdWJzZXQodW5zY2FsZXZpLCBJbXBvcnRhbmNlX3BlcmMgPiAwLjAwMTApICU+JSBzZWxlY3QoVmFyaWFibGUpICNLZWVwIFZhcmlhYmxlcyB0aGF0IGV4cGxhaW4gYXQgbGVhc3QgYSBzbWFsbCBhbW91bnQgWzAuMSVdIG9mIHRoZSBtb2RlbC4gVGhpcyBpcyBhIGxvdyB0aHJlc2hvbGQgZm9yIGluY2x1c2lvbiAsYnV0IHlvdSBjYW4gYWRqdXN0IHRoaXMNCg0KdmFyaWFibGVzX3RvX2tlZXBfMjAyMWIgPSB0KHZhcmlhYmxlc190b19rZWVwXzIwMjEpDQoNCnZhcmlhYmxlc190b19rZWVwXzIwMjIgPSBjb2xuYW1lcyhyb3dfdG9fbmFtZXModmFyaWFibGVzX3RvX2tlZXBfMjAyMWIscm93X251bWJlciA9IDEpKQ0KDQp0cl90cmVhdGVkXzIwMjIgPSB0cl90cmVhdGVkXzIwMjEgJT4lICBzZWxlY3QoV29ydGgsb25lX29mKHZhcmlhYmxlc190b19rZWVwXzIwMjIpLHN0YXJ0c193aXRoKCJUZWFtX2xldl94XyIpKSAja2VlcCBtb2RlbGVkIGltcG9ydGFudCB2YXJpYWJsZXMgYWxvbmcgd2l0aCB0ZWFtIGluZGljYXRvciB2YXJpYWJsZXMNCg0KdGVfdHJlYXRlZF8yMDIyID0gdGVfdHJlYXRlZF8yMDIxICU+JSAgc2VsZWN0KFdvcnRoLG9uZV9vZih2YXJpYWJsZXNfdG9fa2VlcF8yMDIyKSxzdGFydHNfd2l0aCgiVGVhbV9sZXZfeF8iKSkNCg0KaW5wdXRfeF8yMDIyID0gYXMubWF0cml4KHNlbGVjdCh0cl90cmVhdGVkXzIwMjIsIC1Xb3J0aCkpDQoNCmlucHV0X3lfMjAyMiA9IHRyX3RyZWF0ZWRfMjAyMiRXb3J0aA0KDQoNCg0KYGBgDQoNCg0KKioqICAgIA0KDQoNCjIuIFJlLWZpdCBtb2RlbCB3aXRoIHJlZHVjZWQgdmFyaWFibGUgc2NvcGUgIA0KDQpgYGB7cn0NCg0KDQooZmluYWxfZ3JpZF8yMDIxIDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkbnJvdW5kcywNCiAgZXRhID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkZXRhLA0KICBtYXhfZGVwdGggPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGgsDQogIGdhbW1hID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkZ2FtbWEsDQogIGNvbHNhbXBsZV9ieXRyZWUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRjb2xzYW1wbGVfYnl0cmVlLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkbWluX2NoaWxkX3dlaWdodCwNCiAgc3Vic2FtcGxlID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkc3Vic2FtcGxlDQopKQ0KDQooeGdiX21vZGVsXzIwMjIgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeF8yMDIyLA0KICB5ID0gaW5wdXRfeV8yMDIyLA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICB0dW5lR3JpZCA9IGZpbmFsX2dyaWRfMjAyMSwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKSkNCg0KDQp2aXAoeGdiX21vZGVsXzIwMjIsIG51bV9mZWF0dXJlcyA9IDMwKQ0KDQp1bnNjYWxldmkyNCA9IHZpKHhnYl9tb2RlbF8yMDIyLCBtZXRob2Q9Im1vZGVsIikNCg0KdW5zY2FsZXZpMjQkSW1wb3J0YW5jZV9wZXJjID0gd2l0aCh1bnNjYWxldmkyNCxJbXBvcnRhbmNlL3N1bShJbXBvcnRhbmNlKSkgDQoNCnVuc2NhbGV2aTI0DQoNCnNhdmUoeGdiX21vZGVsXzIwMjIsZmlsZSA9ICcyMDIyX1BpdGNoaW5nNXg1X01vZGVsLlJkYXRhJykNCg0KYGBgDQoNCg0KKioqICAgIA0KDQoNCg0KIyMgR2V0IDIwMjIgbGlzdCBvZiBwbGF5ZXJzICANCiMjIyBBcnJhbmdlIHRoZSBEYXRhIHNvIHRoZSBDb2x1bW5zIGFyZSBpbiB0aGUgZXhhY3Qgb3JkZXIgYXMgdGhlIG1vZGVsICAgDQpGaXJzdCBsZXQncyBwcmVwYXJlIGEgZmlsZSBmb3IgcHJlZGljdGluZyBiYXNlZCBvbiBvdXIgbW9kZWwgb2JqZWN0DQoNCmBgYHtyfQ0KDQoNCg0KDQp2YXJpYWJsZXNsYWc9IHJvd190b19uYW1lcyhhcy5kYXRhLmZyYW1lKHQodmFyaWFibGVzX3RvX2tlZXBfMjAyMikpLHJvd19udW1iZXIgPSAxKSAgJT4lIHNlbGVjdCAoc3RhcnRzX3dpdGgoImxhZyIpKQ0KDQp2YXJpYWJsZXNfbm9sYWcgPSBvd21yOjpyZW1vdmVfcHJlZml4KHZhcmlhYmxlc2xhZywibGFnIiAsIHNlcCA9ICJfIikNCg0KRGF0YV9QcmVkaWN0XzIwMjJhID0gZGZfcGl0Y2hpbmdfaW5pdDIgJT4lIHNlbGVjdCAob25lX29mKGNvbG5hbWVzKHZhcmlhYmxlc19ub2xhZykpLFNlYXNvbixwbGF5ZXJpZCkNCg0KY29sbmFtZXMoRGF0YV9QcmVkaWN0XzIwMjJhKSA8LSBwYXN0ZTAoImxhZ18iLCBjb2xuYW1lcyhEYXRhX1ByZWRpY3RfMjAyMmEpKQ0KDQpEYXRhX1ByZWRpY3RfMjAyMmIgPSBkZl9waXRjaGluZ19pbml0MiAlPiUgc2VsZWN0IChvbmVfb2YoY29sbmFtZXModmFyaWFibGVzX25vbGFnKSkpDQoNCmNvbG5hbWVzKERhdGFfUHJlZGljdF8yMDIyYikgPSBjb2xuYW1lcyh2YXJpYWJsZXNsYWcpDQoNCnZhcmlhYmxlc190b19rZWVwXzIwMjJfbm9sYWcgPSB0b3RhbF90cmVhdGVkXzIwMjFfcGl0Y2hpbmcgJT4lIHNlbGVjdChvbmVfb2YodmFyaWFibGVzX3RvX2tlZXBfMjAyMiksU2Vhc29uLHBsYXllcmlkLHN0YXJ0c193aXRoKCJUZWFtX2xldl94XyIpKSU+JSBzZWxlY3QoLW9uZV9vZihjb2xuYW1lcyhEYXRhX1ByZWRpY3RfMjAyMmIpKSkNCg0KDQpEYXRhX3ByZWRpY3RfMjAyMiA9IHNxbGRmKA0KICAiDQogIHNlbGVjdCBhLiosYi4qIGZyb20NCiAgRGF0YV9QcmVkaWN0XzIwMjJhIGEsDQogIHZhcmlhYmxlc190b19rZWVwXzIwMjJfbm9sYWcgYg0KICBvbiBiLnBsYXllcmlkID0gYS5sYWdfcGxheWVyaWQNCiAgYW5kIGIuU2Vhc29uID0gYS5sYWdfU2Vhc29uDQogICINCikgJT4lIHNlbGVjdCgtbGFnX3BsYXllcmlkLGxhZ19TZWFzb24pICU+JQ0KICBmaWx0ZXIoU2Vhc29uID09IDIwMjEpICU+JSANCiAgc2VsZWN0KG9uZV9vZih2YXJpYWJsZXNfdG9fa2VlcF8yMDIyKSxzdGFydHNfd2l0aCgiVGVhbV9sZXZfeF8iKSkNCg0KbmFtZXModmFyaWFibGVzX3RvX2tlZXBfMjAyMl9ub2xhZykNCg0KDQpgYGANCg0KDQoqKiogICAgDQoNCg0KIyMgQ3JlYXRlIFByZWRpY3Rpb25zIGZvciBNb2RlbA0KIyMjIFJ1biBQcm9qZWN0aW9ucyBvbiBQbGF5ZXJzIHdobyBQbGF5ZWQgaW4gMjAyMQ0KVGhpcyBpcyB0aGUgcmF3IHByZWRpY3Rpb24gc2NvcmUgcGVyIElQIGZvciBlYWNoIHBpdGNoZXIgIA0KYGBge3J9DQoNCnBpdGNoaW5nX3ByZWRpY3Rpb25zID0gYXMuZGF0YS5mcmFtZShwcmVkaWN0KHhnYl9tb2RlbF8yMDIyLERhdGFfcHJlZGljdF8yMDIyKSkNCg0KbmFtZXMocGl0Y2hpbmdfcHJlZGljdGlvbnMpID0gYygiUHJlZGljdF9TY29yZSIpDQoNCkRhdGFfcHJlZGljdF8yMDIyX3dfUGl0Y2hpbmdfUHJlZGljdGlvbnMgPSBjYmluZChEYXRhX3ByZWRpY3RfMjAyMixwaXRjaGluZ19wcmVkaWN0aW9ucykgJT4lIHNlbGVjdChwbGF5ZXJpZCxQcmVkaWN0X1Njb3JlKQ0KDQpoZWFkKERhdGFfcHJlZGljdF8yMDIyX3dfUGl0Y2hpbmdfUHJlZGljdGlvbnMpDQoNCmBgYA0KDQoNCioqKiAgICANCg0KIyMjIExvYWQgaW4gTGF0ZXN0IDIwMjIgUHJvamVjdGlvbnMgZm9yIElubmluZ3MgUGl0Y2hlZCAgDQpEb3dubG9hZGVkIGZyb20gRmFuR3JhcGhzIFtoZXJlLl0oaHR0cHM6Ly93d3cuZmFuZ3JhcGhzLmNvbS9wcm9qZWN0aW9ucy5hc3B4P3Bvcz1hbGwmc3RhdHM9cGl0JnR5cGU9YXRjJnRlYW09MCZsZz1hbGwmcGxheWVycz0wKSAgDQoNCmBgYHtyfQ0KTGF0ZXN0XzIwMjJfcGl0Y2hpbmdkYXRhX0ZQID0gcmVhZF9jc3YoIkZhbkdyYXBoX0ZhbnRhc3lfQmFzZWJhbGxfUGl0Y2hpbmcuY3N2IikNCg0KTGF0ZXN0XzIwMjJfcGl0Y2hpbmdkYXRhX0ZQDQoNCmBgYA0KDQoNCioqKiAgICANCg0KDQpgYGB7ciwgd2FybmluZyA9IEZhbHNlfQ0KDQoNClBpdGNoaW5nX0RhdGFfTm9uQWRqX1Byb2plY3Rpb25zID0gc3FsZGYoDQogICINCiAgc2VsZWN0IGEuKixiLlByZWRpY3RfU2NvcmUNCiAgZnJvbSBMYXRlc3RfMjAyMl9waXRjaGluZ2RhdGFfRlAgYSANCiAgbGVmdCBqb2luIA0KICBEYXRhX3ByZWRpY3RfMjAyMl93X1BpdGNoaW5nX1ByZWRpY3Rpb25zIGINCiAgb24gYS5wbGF5ZXJpZCA9IGIucGxheWVyaWQNCiAgIg0KKSAlPiUgZmlsdGVyKEFEUDwzNzAgfCBpcy5uYShQcmVkaWN0X1Njb3JlKT09RikNCg0KDQpQaXRjaGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9ucyA9DQpQaXRjaGluZ19EYXRhX05vbkFkal9Qcm9qZWN0aW9ucyAlPiUgDQogIG11dGF0ZSgNCiAgICBBdmdfSVAgPSA2MCwNCiAgICBBZGpQcmVkaWN0X1Njb3JlX3JhdyA9IGlmZWxzZShpcy5uYShQcmVkaWN0X1Njb3JlKSxOQSxQcmVkaWN0X1Njb3JlKihJUC9BdmdfSVApKSwNCiAgICBtYXhfcHJlZHNjb3JlPSBtYXgoQWRqUHJlZGljdF9TY29yZV9yYXcsbmEucm0gPSBUKSwNCiAgICBBZGpQcmVkaWN0X1Njb3JlID0gaWZlbHNlIChpcy5uYShBZGpQcmVkaWN0X1Njb3JlX3JhdyksTkEsQWRqUHJlZGljdF9TY29yZV9yYXcgKjEwMC9tYXhfcHJlZHNjb3JlKQ0KICApICU+JSBzZWxlY3QgKE5hbWUsQURQLFdBUixBZGpQcmVkaWN0X1Njb3JlKQ0KICANCg0KZ2dwbG90Mjo6cXBsb3QoUGl0Y2hpbmdfRGF0YV9BZGpfUHJvamVjdGlvbnMkQWRqUHJlZGljdF9TY29yZSwgbWFpbj0iUHJlZGljdGlvbnMiKSArIGdlb21faGlzdG9ncmFtKGNvbG91cj0iYmxhY2siLCBmaWxsPSJncmV5IikgKyB0aGVtZV9idygpDQoNCg0KYGBgDQoNCg0KKioqICAgIA0KDQojIDIwMjIgUHJvamVjdGlvbnMgRnVsbCAgDQojIyBUYWJsZSBvZiBQaXRjaGluZyBQcm9qZWN0aW9ucyAoUGxheWVycyB3aG8gRGlkbid0IFBsYXkgaW4gMjAyMSAtIFJlY2lldmUgYW4gTkEpICANCkFkalByZWRpY3RfU2NvcmUgYXJlIG5vcm1hbGl6ZWQgdG8gMTAwDQpgYGB7cn0NCg0KdGFibGVleHBvcnQgPQ0KUGl0Y2hpbmdfRGF0YV9BZGpfUHJvamVjdGlvbnMgJT4lDQogIGFycmFuZ2UgKEFEUCxXQVIpICU+JSANCiAga2JsKCkgJT4lIA0KIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiLCJjb25kZW5zZWQiLCJyZXNwb25zaXZlIiksZnVsbF93aWR0aCA9IEYsZml4ZWRfdGhlYWQgPSBUKQ0KDQpzYXZlX2thYmxlKHRhYmxlZXhwb3J0LGZpbGUgPSAiUGl0Y2hpbmc1eDUuaHRtbCIpDQoNCnRhYmxlZXhwb3J0DQpgYGANCg0KDQoNCg0KDQoNCjwvaHRtbD4NCg==